闭关修炼180天 -- 手写SpringMVC框架(迷你版)
SpringMvc知识须知
MVC设计模式
- Model(模型):模型包含业务模型和数据模型,数据模型⽤于封装数据,业务模型⽤于处理业 务。
- View(视图): 通常指的就是我们的 jsp 或者 html。作⽤⼀般就是展示数据的。通常视图是依据 模型数据创建的。
- Controller(控制器): 是应⽤程序中处理⽤户交互的部分。作⽤⼀般就是处理程序逻辑的。
springMvc请求执行流程
- ⽤户发送请求⾄前端控制器DispatcherServlet
- DispatcherServlet收到请求调⽤HandlerMapping处理器映射器
- 处理器映射器根据请求Url找到具体的Handler(后端控制器),⽣成处理器对象及处理器拦截 器(如果 有则⽣成)⼀并返回DispatcherServlet
- DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
- 处理器适配器执⾏Handler
- Handler执⾏完成给处理器适配器返回ModelAndView
- 处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个 底层对 象,包括 Model 和 View
- 前端控制器请求视图解析器去进⾏视图解析,根据逻辑视图名来解析真正的视图。
- 视图解析器向前端控制器返回View
- 前端控制器进⾏视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域
- 前端控制器向⽤户响应结果

SpringMVC九大核心组件
- 1.HandlerMapping(处理器映射器):找到请求响应的处理器Handler和Interceptor
- 2.HandlerAdapter(处理器适配器):要让固定的Servlet处理方法调用Handler来处理
- 3.HandlerExceptionResolver(异常处理器),用于处理Handler产生的异常情况
- 4.ViewResolver(视图解析器),将Sting类型的视图名解析为View类型的视图
- 5.RequestToViewNameTranslator,在请求域中获取ViewName,当Handler处理完后,没有设置View,那便从这个组件中从请求中获取ViewName
- 6.LocaleResolver,区域化国际化组件
- 7.ThemeResolver 主题解析器
- 8.MultipartResolver多文件上传解析器
- 9.FlashMapManager,用于重定向时的参数传递
自定义SpringMVC框架
大致流程
- 引进javax.servlet坐标,创建servlet包,创建HttpServlet的实现类MyServlet
- 创建annotation包,创建四个注解:@MyController,@MyRequestMapping,@MyService,@MyAutowired
- 重写HttpServlet的几个方法init(),doPost(),doGet()
- 在init()方法中加载解析springMvc.xml文件
- 在init()方法中完成注解的功能增强。
- 在init()方法中初始化ioc容器
- 在init()方法中完成依赖的注入
- 构造一个处理器映射器HandlerMapping,将我们处理好的url和Method建立映射关系
- 处理权限关系
项目结构

代码实现
1.pom文件主要内容
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties> <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency> <dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency> <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies> <build>
<plugins>
<!--编译插件定义编译细节-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>8</source>
<target>8</target>
<encoding>utf-8</encoding>
<!--告诉编译器,编译的时候记录下形参的真实名称-->
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin> <plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
2.web.xml 以及 springmvc.properties
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app>
<display-name>Archetype Created Web Application</display-name> <servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.zae.frame.servlet.MyServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>springmvc.properties</param-value>
</init-param>
</servlet> <servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping> </web-app>
scanPackage=com.zae
3.五个注解的定义
import java.lang.annotation.*; @Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
String value() default "";
}
import java.lang.annotation.*; @Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
String value() default "";
}
import java.lang.annotation.*; @Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
String value() default "";
}
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
String value() default "";
}
/**
* 1.当未添加该注解时,表明该方法不加入权限控制,所有请求均可访问
* 2.当该注解放置在某个类上,则表明该类下的所有方法都可以被该注解指定的用户访问
* 3.当该注解放置在某个方法时,则以该方法上的用户指定为准
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Security {
//配置用户名称,指定的用户可以访问
String [] value() default {"/"};
}
4.handler实体的定义
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern; /**
* 封装映射关系
*/
public class Handler {
//controller
private Object controller;
//方法
private Method method;
//url的正则表达式
private Pattern pattern;
//存储参数字段以及其的入参位置
private Map<String,Integer> handlerMapping;
//存储有权限访问的用户
private String [] securityUser; public Handler(Object controller, Method method, Pattern pattern) {
this.controller = controller;
this.method = method;
this.pattern = pattern;
this.handlerMapping = new HashMap<String, Integer>();
} public String[] getSecurityUser() {
return securityUser;
} public void setSecurityUser(String[] securityUser) {
this.securityUser = securityUser;
} public Object getController() {
return controller;
} public void setController(Object controller) {
this.controller = controller;
} public Method getMethod() {
return method;
} public void setMethod(Method method) {
this.method = method;
} public Pattern getPattern() {
return pattern;
} public void setPattern(Pattern pattern) {
this.pattern = pattern;
} public Map<String, Integer> getHandlerMapping() {
return handlerMapping;
} public void setHandlerMapping(Map<String, Integer> handlerMapping) {
this.handlerMapping = handlerMapping;
}
}
5.MyServlet核心类的实现
import com.zae.frame.annotation.*;
import com.zae.frame.pojo.Handler;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern; public class MyServlet extends HttpServlet {
//配置文件信息:springmvc.xml
Properties properties = new Properties();
//存放扫描包下的全限类名
List<String> classList = new ArrayList<String>();
//ioc容器,存放实例对象
Map<String,Object> beanMap = new HashMap<String,Object>();
//存放Handler的集合
List<Handler> handlerList = new ArrayList<Handler>(); @Override
public void init(ServletConfig config){
//1.加载配置文件:springmvc.xml
String path = config.getInitParameter("contextConfigLocation");
doLoanConfig(path); //2.扫描包下所有的类
doScan(properties.getProperty("scanPackage"));
//3.初始化bean对象,基于注解,加入ioc容器
doInstance();
//4.完成依赖的注入@MyAutowired
doAutowired();
//5.构建好一个HandlerMapping,完成url和method之间的映射关系
initHandleMapping();
//6.处理用户权限问题
doSecurity(); System.out.println("迷你版SpringMvc初始化完成....");
} /**
* 完成Controller层的用户访问权限问题
*/
private void doSecurity() {
if (handlerList.size() == 0){return;} for(Handler handler:handlerList){
Class<?> aClass = handler.getController().getClass();
String [] securityUserArr = null;
//1.当controller类上有@Security注解时,则改类中所有的方法都加入权限控制,以类上的@Security里面的value为准
if(aClass.isAnnotationPresent(Security.class)){
Security annotation = aClass.getAnnotation(Security.class);
securityUserArr = annotation.value();
}
//2.当方法上存在@Security注解时,则以方法的@Security中的value为准,覆盖类上声明的那个权限注解
Method method = handler.getMethod();
if(method.isAnnotationPresent(Security.class)){
Security annotation = method.getAnnotation(Security.class);
securityUserArr = annotation.value();
}
//3.如果无论controller类还是其中的方法都没有加Security注解,此时securityUserArr==null,那么可以任意访问,不会拦截,
//4.如果controller层加了Security注解,但是没有重新定义value值或者value值声明为{"/"},那么此时securityUserArr=={"/"},会拦截所有用户的请求
handler.setSecurityUser(securityUserArr);
}
} /**
* 完成url与方法之间的映射
*/
private void initHandleMapping() {
if(beanMap.size() == 0){return;}
for(Map.Entry<String,Object> map:beanMap.entrySet()){
Class<?> aClass = map.getValue().getClass();
String baseUrl = "";
//当controller类上有MyRequestMapping注解时,基础url为value值
if(aClass.isAnnotationPresent(MyRequestMapping.class)){
MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class);
baseUrl = annotation.value();
}
//获取Controller的所有方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
//当方法上有MyRequestMapping注解时,获取其value值
if(method.isAnnotationPresent(MyRequestMapping.class)){
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
//请求url
String url = baseUrl+annotation.value();
Pattern compile = Pattern.compile(url);
Handler handler = new Handler(map.getValue(),method,compile); //计算方法的参数位置信息
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
//当参数为HttpServletRequest和HttpServletResponse时,key存储的是参数的类型名
if(parameters[i].getType() == HttpServletRequest.class || parameters[i].getType() == HttpServletResponse.class){
handler.getHandlerMapping().put(parameters[i].getType().getSimpleName(),i);
}else{
//当其他情况的时候,key存储的是参数名称
handler.getHandlerMapping().put(parameters[i].getName(),i);
}
}
//将handler存储起来
handlerList.add(handler);
}
}
}
} /**
* 依赖注入
*/
private void doAutowired() {
if(beanMap.size()==0){return;}
for(Map.Entry<String,Object> mapEntry:beanMap.entrySet()){
String key = mapEntry.getKey();
Object value = mapEntry.getValue();
Class<?> aClass = value.getClass();
//获取该类下所有的属性
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
//判断属性上是否存在MyAutowired注解
if(declaredField.isAnnotationPresent(MyAutowired.class)){
MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class);
//获取注解里面的value值
String name = annotation.value();
//设置暴力访问
declaredField.setAccessible(true);
if("".equals(name.trim())){
//当注解的value值为空时,获取属性类型的全限类名,使用类型注入依赖
name = declaredField.getType().getName();
}
try {
//给该属性赋值
declaredField.set(value,beanMap.get(name));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
//依赖赋值结束后,重新加入map集合中
beanMap.put(key,value);
}
} /**
* 基于注解完成ioc的初始化
*/
private void doInstance() {
if(classList.size()==0){return;} for (String className : classList) { try {
//根据全限类名获取类对象
Class<?> aClass = Class.forName(className);
if(aClass.isAnnotationPresent(MyService.class)){
MyService annotation = aClass.getAnnotation(MyService.class);
String value = annotation.value();
Object beanObj = aClass.newInstance();
if("".equals(value)){
//当service注解为空时,使用首字母小写的类名
value = firstToLower(aClass.getSimpleName()); beanMap.put(value,beanObj);
}else{
//当service的value不为空时,使用注解里面的value值
beanMap.put(value,beanObj);
} //面向接口开发,以接口全限类名作为id放入容器(针对service层存在接口)
Class<?>[] interfaces = aClass.getInterfaces();
if(interfaces!=null){
for (Class<?> anInterface : interfaces) {
beanMap.put(anInterface.getName(),aClass.newInstance());
}
} }else if(aClass.isAnnotationPresent(MyController.class)){
//MyController注解的类使用首字母小写的类名
Object beanObj = aClass.newInstance();
beanMap.put(firstToLower(aClass.getSimpleName()),beanObj);
}else{
continue;
} } catch (Exception e) {
e.printStackTrace();
}
}
} private String firstToLower(String source){
if(source==null){return null;}
char[] chars = source.toCharArray();
if(chars[0]>='A' && chars[0]<='Z'){
chars[0] += 32;
}
return String.valueOf(chars);
}
/**
* 扫描包下所有的类
* @param scanPackage
*/
private void doScan(String scanPackage) {
String scanPackagePath =Thread.currentThread().getContextClassLoader().getResource("").getPath()+scanPackage.replaceAll("\\.","/");
File fileScan = new File(scanPackagePath);
File[] files = fileScan.listFiles();
for (File file : files) {
if(file.isDirectory()){
//如果是文件夹,就继续递归
doScan(scanPackage+"."+file.getName()); }else if(file.getName().endsWith(".class")){
String className = scanPackage+"."+file.getName().replaceAll(".class","");
classList.add(className);
}
}
} /**
* 加载springmvc.properties配置文件
* @param path
*/
private void doLoanConfig(String path) {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(path);
try {
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
} } @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
//设置中文编码格式
resp.setContentType("text/html;charset=UTF-8");
doPost(req,resp);
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Handler handler = getHandler(req);
if(handler == null){
resp.getWriter().write("404 No fount");
return;
}
//检查用户权限问题
if(!checkSecurity(handler,req,resp)){return;} //参数绑定工作
Class<?>[] parameterTypes = handler.getMethod().getParameterTypes(); //根据上述长度创建一个新的数组,参数传递时反射调用
Object[] objParam = new Object[parameterTypes.length]; //为了向参数数组中塞值,保证形参和方法的参数顺序一致
Map<String, String[]> parameterMap = req.getParameterMap();
//拿到方法的入参顺序
Map<String, Integer> handlerMapping = handler.getHandlerMapping();
for(Map.Entry<String,String[]> map:parameterMap.entrySet()){
String join = StringUtils.join(map.getValue(), ",");
String key = map.getKey();
//如果字段名字匹配上了,则进行赋值操作
if(parameterMap.containsKey(key)){
objParam[handlerMapping.get(key)] = join;
}
} //处理HttpServletRequest和HttpServletResponse
int reqIndex = handlerMapping.get(HttpServletRequest.class.getSimpleName()); int respIndex = handlerMapping.get(HttpServletResponse.class.getSimpleName()); objParam[reqIndex] = req; objParam[respIndex] = resp; Method method =handler.getMethod();
Object controllerObj = handler.getController();
try {
//进行方法调用
method.invoke(controllerObj,objParam);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} } /**
* 检查用户权限问题
* @param req
* @return
*/
private Boolean checkSecurity(Handler handler,HttpServletRequest req,HttpServletResponse resp) throws IOException {
//当securityUser == null时,没有加权限控制,任意请求都能访问
if(handler.getSecurityUser() == null){
return true;
} //当securityUser == {/},任意请求都不能访问
if("/".equals(handler.getSecurityUser()[0])){
resp.getWriter().write("您无权访问!请联系管理员");
return false;
} //前端的参数,访问的用户
String username = req.getParameter("username");
//当securityUser存在值时,则进行校验
List<String> userList = Arrays.asList(handler.getSecurityUser());
if(userList.contains(username)){
return true;
}else{
resp.getWriter().write("您无权访问!请联系管理员");
return false;
}
} /**
* 根据请求获取Handler对象
* @param req
* @return
*/
private Handler getHandler(HttpServletRequest req){
if(handlerList.isEmpty()){return null;} String url = req.getRequestURI(); for (Handler handler : handlerList) {
//判断url和正则是否匹配
Matcher matcher = handler.getPattern().matcher(url);
if(!matcher.matches()){continue;}
return handler;
}
return null;
}
}
6.使用端Service以及Controller(测试使用,不再关联数据库了)
public interface TestService {
String start(String username);
}
import com.zae.demo.service.TestService;
import com.zae.frame.annotation.MyService; @MyService
public class TestServiceImpl implements TestService {
@Override
public String start(String username) {
System.out.println("访问的用户为:"+username);
return username;
}
}
import com.zae.demo.service.TestService;
import com.zae.frame.annotation.MyAutowired;
import com.zae.frame.annotation.MyController;
import com.zae.frame.annotation.MyRequestMapping;
import com.zae.frame.annotation.Security; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; @Security({"wangsulong","xuezhiqian"})
@MyController
@MyRequestMapping("/demoSecurity")
public class TestSecurityController { @MyAutowired
private TestService testService; /**
* 方法中有@Security,则覆盖类定义的权限注解
* @param request
* @param response
* @param username
* @return
*/
@Security({"xusong"})
@MyRequestMapping("/startSecurityOne")
public String startSecurityOne(HttpServletRequest request, HttpServletResponse response,String username){
return testService.start(username);
} /**
* 方法中没有@Security,则使用类里面的权限注解定义的
* @param request
* @param response
* @param username
* @return
*/
@MyRequestMapping("/startSecurityTwo")
public String startSecurityTwo(HttpServletRequest request, HttpServletResponse response,String username){
return testService.start(username);
} /**
* @Security中的value没有设定值,则拦截所欲
* @param request
* @param response
* @param username
* @return
*/
@Security
@MyRequestMapping("/startSecurityThree")
public String startSecurityThree(HttpServletRequest request, HttpServletResponse response,String username){
return testService.start(username);
}
}
自定义SpringMvc框架至此结束

文章知识点输出来源:拉勾教育Java高薪训练营
2021第一天,我是帝莘,期待和你的技术交流以及思想碰撞
闭关修炼180天 -- 手写SpringMVC框架(迷你版)的更多相关文章
- 闭关修炼180天--手写持久层框架(mybatis简易版)
闭关修炼180天--手写持久层框架(mybatis简易版) 抛砖引玉 首先先看一段传统的JDBC编码的代码实现: //传统的JDBC实现 public static void main(String[ ...
- 闭关修炼180天--手写IOC和AOP(xml篇)
闭关修炼180天--手写IOC和AOP(xml篇) 帝莘 首先先分享一波思维导图,涵盖了一些Spring的知识点,当然这里并不全面,后期我会持续更新知识点. 在手写实现IOC和AOP之前(也就是打造一 ...
- (二)springMvc原理和手写springMvc框架
我们从两个方面了解springmvc执行原理,首先我们去熟悉springmvc执行的过程,然后知道原理后通过手写springmvc去深入了解代码中执行过程. (一)SpringMVC流程图 (二)Sp ...
- 手写SpringMVC 框架
手写SpringMVC框架 细嗅蔷薇 心有猛虎 背景:Spring 想必大家都听说过,可能现在更多流行的是Spring Boot 和Spring Cloud 框架:但是SpringMVC 作为一款实现 ...
- 手写SpringMVC框架(三)-------具体方法的实现
续接前文 手写SpringMVC框架(二)结构开发设计 本节我们来开始具体方法的代码实现. doLoadConfig()方法的开发 思路:我们需要将contextConfigLocation路径读取过 ...
- 手写SpringMVC框架(二)-------结构开发设计
续接前文, 手写SpringMVC框架(一)项目搭建 本节我们来开始手写SpringMVC框架的第二阶段:结构开发设计. 新建一个空的springmvc.properties, 里面写我们要扫描的包名 ...
- 手写一套迷你版HTTP服务器
本文主要介绍如何通过netty来手写一套简单版的HTTP服务器,同时将关于netty的许多细小知识点进行了串联,用于巩固和提升对于netty框架的掌握程度. 服务器运行效果 服务器支持对静态文件css ...
- 手写SpringMVC框架(一)-------项目搭建
SpringMVC处理请求的大致流程: 我们来开始着手手写一个SpringMVC框架. 新建一个springMVC项目,流程参见 SpringMVC框架搭建流程 引入servlet相关的jar包: & ...
- 纯手写SpringMVC框架,用注解实现springmvc过程
闲话不多说,直接上代码! 1.第一步,首先搭建如下架构,其中,annotation中放置自己编写的注解,主要包括service controller qualifier RequestMapping ...
- 二. 手写SpringMVC框架
1.1 新建DispatcherServlet 1.2 在src目录下,新建applicationContext.xml <?xml version="1.0" encodi ...
随机推荐
- C# 使用RabbitMQ消息队列
参考文章 https://www.cnblogs.com/kiba/p/11703073.html和https://www.cnblogs.com/longlongogo/p/6489574.html ...
- 发布.net core应用程序并部署到IIS上
一.在项目里右击选择发布点击启动配置如下图所示 二.在打开的发布选项选择 配置 Release或DeBug ,目标框架选择对应的.net Core版本默认就行,部署模式有两种选择 1.框架依赖---- ...
- vim 从嫌弃到依赖(2)——vim 模式
在上一篇文章中我们获取到了neovim 并对它进行了基础配置.现在已经具备一般编辑器的基本功能了.让我们先学会如何使用vim基本功能进行编辑,后面再看如何进行配置,以达到某某IDE或者编辑器的效果 v ...
- TienChin 渠道管理-工程创建
因为本文章主要围绕着项目开发进行,所以前言不做开头,直接上内容. 添加字段 我们的渠道表,我看到若依脚手架当中有一个是否删除的标志字段,所以我这里也添加一下: ALTER TABLE `tienchi ...
- Python 多线程爬取西刺代理
西刺代理是一个国内IP代理,由于代理倒闭了,所以我就把原来的代码放出来供大家学习吧. 首先找到所有的tr标签,与class="odd"的标签,然后提取出来. 然后再依次找到tr标签 ...
- AI自动生成视频保姆级教程,还能赚包辣条哦~
友友们,小卷今天给大家分享下如何通过AI自动生成视频,只需要3分钟就能做出一个视频,把视频发到B站.抖音.西瓜上,还能赚包辣条哦~ 文末给大家准备了AI变现的案例及AIGC知识库,记得领取哦! 1.收 ...
- RabbitMQ基础学习Full版
RabbitMQ 消息队列在软件中的应用场景 异步处理上(优于原先的方式) 为什么优于呢? 首先,通常情况下,如上图我们其实不用消息队列的情况下,其实也可以不用100ms,不用allof即可 那么优势 ...
- 小知识:使用MOS下载Oracle介质快速参考
之前对选Release.Patch Set.PSU都有专门的文档,现在早已简化,针对这些以及之后RU.RUR等都包含在MOS文档:2118136.2 Assistant: Download Refer ...
- 从零开始的react入门教程(七),react中的状态提升,我们为什么需要使用redux
壹 ❀ 引 在前面的文章中,我们了解到react中的数据由props与State构成,数据就像瀑布中的水自上而下流动,属于单向数据流.而这两者的区别也很简单,对于一个组件而言,如果说props是外部传 ...
- JS leetcode 寻找数组的中心索引 题解分析
壹 ❀ 引 今天是的题目来自leetcode的724. 寻找数组的中心索引,做完之后我感觉自己像个憨憨,题目描述如下: 给定一个整数类型的数组 nums,请编写一个能够返回数组"中心索引&q ...