开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第32天,点击查看活动详情
什么是react
react是用于构建用户界面的js框架,是一个将数据渲染为html视图的开源js库(主要用于操作dom呈现页面)
react的特点
- 声明式设计:react采用声明式UI,使代码更易于调试
- 高效:使用虚拟dom+diff算法,尽量减少与真实DOM的交互
- 灵活:react可以与已知的库或框架很好的配合
- JSX:JSX是js语法的扩展,是生成虚拟dom的
- 组件:通过react构建组件,使得代码更加容易得到复用
- 单向响应的数据流:react实现了单向响应的数据流
- 在ReactNative中可以使用react语法进行移动端开发
学习react的原因
- 原生js操作dom繁琐,效率低
- 使用js直接操作dom,浏览器会进行大量的重排重绘
- 原生js没有组件编码方案,代码复用率低
react和其他框架相比的优点
- 组件的组合模式
- 单向数据流的设计,数据传递路线更清楚,不容易出现错误,出现错误了,更容易找到错误
- 高效的性能,react实现原理:虚拟dom是一个轻量级的react抽象出来的一个对象,用来描述真实dom应该是什么样子,应该怎么呈现,通过虚拟dom去更新真实的dom,由虚拟dom管理真实dom的更新。diff算法可以提升虚拟dom渲染成真实dom的速度,增加效率。
- 分离的框架设计:react.js现在的版本已经将源码分开为reactDOM和reactjs,这就意味着react不仅仅能够在web端工作,甚至可以在服务端(nodejs)和native端工作
diff算法(三大策略)
- 策略一:tree diff:webUI中DOM节点跨层级的移动操作特别少,可以忽略补给。
- 策略二:component diff:拥有相同类的两个组件,生成相似的树形结构。(可以直接对象虚拟dom,也可以通过shouldComponentUpdate来进行判断) 拥有不同类的两个组件,生成不同的属性结构,(跟原来不同的直接替换掉)
- 策略三:element diff:对于同一层级的一组子节点,通过唯一key区分(key类似于ID,需要手动添加)
react环境搭建
- 使用套件搭建环境(不咋用)
- 使用脚手架create-react-app构建react开发环境。
- cnpm i -g create-react-app //全局安装
- create-react-app 自定义文件名称 //创建项目,名字不能包含大写字母
- cd 项目名 //可以切换到自己项目,如果就在本项目上可以不用
- cnpm start //项目运行
- 多页面开发:使用create-react-app创建的环境默认是单页面应用开发环境,如果要使用该命令创建多页面的程序,只需要修改相关配置即可。
- 使用create-react-app创建项目
- 在项目目录下运行命令cnpm runeject(显示出隐藏的配置文件)此命令对项目工程是不可逆的,且只能执行一次,运行后,package.js会被更新,工程下会多出config目录,其中webpack有两个配置文件,分别对应开发环境和生产环境。(/config/webpack.config.dev.js 和/config/webpack.config.prod.js)
- 修改webpack.config.dev.js配置
- 使用webpack搭建环境(手动搭建):
- 全局安装:首先要安装Node.js。Node.js自带了软件包管理器npm,webpack需要node.js v0.6以上的支持,建议使用最新版Node.js
- 使用npm安装全局webpack cnpm i webpack -g
- 此时webpack已经安装到了全局环境下,可以通过命令行webpack-h试试。
- 手动创建一个webpack项目
- 创建一个package.json文件,用于保存项目版本,依赖关系等 cnpm init
- 在当前目录下安装webpack cnpm i webpack -D
- 安装项目依赖(安装react和react-dom)cnpm i react react-dom
- 安装各种loader cnpm i -D css-loader style-loader url-loader file-loader
- 安装babel-loader cnpm i -D babel-loader @babel/preset-env @babek/preset-react
- 在根目录创建webpack.config.js文件:配置信息
- 打包webpack --watch
- 将新生成的public文件里创建一个html把这个打包完的js引入。
- 设置组件,创建src文件夹,设置js文件,index.js是主入口文件 App.js是组件
虚拟dom和真实dom的区别
react的核心机制之一就是可以在内存中创建虚拟的DOM元素,react利用虚拟DOM来减少实际DOM的操作从而提升性能。
- 虚拟DOM不仅进行重排重绘操作。
- 虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后在真实DOM中进行回流和重绘,减少过多DOM节点的回流和重绘
- 虚拟DOm比真实dom体积小,操作时相对来说消耗性能少。虚拟Dom最终会转换成真实dom
- 查找真实dom操作,用document.get...查询的是整个节点数。ParentNode.querySelector()和ParentNode.querySelectorAll()是有范围地查询ParentNode下的节点,过程中是需要根据传入的参数来比对节点上的属性。。虚拟DOM查找操作this.refs对象中的key为refName的属性。
JSX
jsx是react重病用来生成虚拟dom的,它的特点
- 定义虚拟dom时,不要写引号
- 标签中混入js表达式时要用{}
- 样式的类名指定不要用class,要用className
- 行内样式,要用style={{“color”:“#f00”}}的形式,连接属性用小驼峰命名
- 虚拟dom必须只能有一个根标签。
- 标签必须闭合
- 标签首字母,为小写,会将转为html同名元素,若无该元素会报错,若为大写,将自动识别为组件,就去渲染对应的组件,若没有该组件,则报错。
react组件
类式组件
使用类创建组件,必须继承react内置的一个组件react.Component,调用时框架会自动生成新对象(先引入react)
必须要有render()方法,可以生成虚拟的dom该方法被定义在类的原型对象上,供实例使用。
在调用ReactDom.render()之后发生了什么?
react解析组件标签,找到组件,发现组件是一个类定义的组件,随后new出来一个该类的实例,并调用这个实例原型上的render方法,将render方法返回的虚拟dom转为真实dom,随后显示在页面中,由于render方法是由该类的实例调用的,所以render中的this,指向的是由类创建的实例对象即组件实例对象
react类组件中自定义的方法函数中this丢失的问题
正常情况下的this是指向调用该方法的实例对象,但是在react中调用该方法是并没有直接调用,例如:this.handleClick(),而是以参数的形式将该方法函数传入,作为回调函数有react框架直接调用,同时又因为类中的方法在定义时默认开启了局部的严格模式,导致this不能指向window,所以在自定义方法中调用this为undefined导致丢失。
如何解决上述this丢失的问题
- constructor
2. 利用bind方法改变this指向
3. 利用箭头函数的this指向,指向箭头函数创建时所在的上下文对象
4. 把自定义方法写成箭头函数,这种也比较常用
react组件对象四大属性
- state
- props
- context
- refs
state
state控制组件更新的(数据内容的修改)状态。(state里面存储的其实就是从服务器拿到的数据,组件需要的数据,当数据发生变动时需要重新渲染)state只有类式组件才会有,因为state是组件实例对象中的,组件实例对象是由类生成的。所有只有在类式组件中才具有state属性。
state必须是一个对象,默认值是null,它是组件实例对象继承React。Component来的,所以要在当前组建的构造函数进行初始化,就是constructor,但是constructor可以省略,省略的话直接写state这个属性放在class内部顶层即可。
state的工作原理,工作流程
render方法输出虚拟dom,虚拟dom转为真实dom,再在真实dom上注册事件,事件触发setState修改数据,在每次调用setState方法时,react会自动执行render方法来更新虚拟dom,如果组件已经被渲染,那么还会更新到dom中去。
在想要修改更新的时候调用setState方法即可。组件状态发生改变时,可以通过this.setState修改状态,setState方法支持按需修改,如state有两个字段,仅当setState传入的对象包含字段key才会修改属性,每次调用setState会导致重新渲染调用render方法,直接修改state不会重新渲染组件。
setState的工作原理
通知react数据变化的方法是调用setState(data:callback),这个方法是通过合并data到this.state,并重新渲染组件,渲染完成后,调用可选的callback回调,大部分情况下不需要提供callback,因为react会负责把界面更新到最新状态。
setState是同步的还是异步的
在同步任务中,setState是异步的,但是在异步任务中,setState是同步的。setState被设置成异步操作,是为了防止在多次大量修改状态时,长时间占用线程,其他任务无法发起,造成虚拟dom假死的问题,在异步任务中同步执行是因为本身setState就处在异步任务之中,不会影响其他任务
props
props相对于组件来说是外来属性,使用props可以从组件外部向组件内部传值,类似于函数的传参,它经常用于组件之间的传值,一种父级向子级传递数据的方式。
语法:父组件
子组件(接值):
如何对props进行类型限制
- 第一种:引入propTypes依赖包, cnpm i proptypes --save-dev,用于对组件标签属性进行显示。
如果传过来的props类型不符合的话会在控制台报错。
2. 第二种:通过设置数据默认值defaultProps
如果在类当中直接添加属性则是添加给了类的实例对象,如果使用 static (静态进行声明则该属性就会给类自己
state与props的区别
区别一:
- state是在组建内部作为组件更新的,
- props是组件外部作为组件之间值的传递的
区别二: - state可以通过setState进行修改
- props是只读不能修改的。
react中组件之间传值
- 父组件向子组件传值:通过props
- 父组件向更深的子组件传值:
- 通过props一层一层往下传递
- 通过context传值。首先使用react.createContext()创建一个context组件,然后在使用创建的这个context对象.Provider里的value={},向下传递数据,最后任何一个后代组件,只需要通过contextType()方法,访问context创建的这个对象,就可以获取到数据。
- 组件组合,将底层组件在高层组件中导入,传入数据,然后将组件通过props传入需要渲染的组件中
3. 子组件向父组件传值:使用回调函数传值。在子组件中创建一个箭头函数,然后将函数通过props传递给父组件,父组件接收该函数,并且调用,调用时可以向其中传入参数。
4. 兄弟组件之间传值:第一个子组件通过回调函数传给父组件,父组件在将值用props传给子组件。
5. 没有关系的组件之间传值:使用redux或react-redux传递。
- redux,将状态集中进行管理。redux的工作流程。首先,react组件从store中获取原始的数据通过getState方法,然后渲染,当react中数据发生改变时,react 就需要分发action,在组件内通过dispatch分发action,让action携带新的数据值派发给store,store将action发给reducers函数,reducers函数接收到两个参数一个是state,一个是action。通过type来进行判断对数据做处理,然后将处理完的数据作为新的state发送给store。最后通过subscribe在组件内监听新的state,从而更新组件内的数据。达到页面更新的目的。
- 通过react-redux可以实现数据监听并更新到组件并重新渲染(首先使用provider包裹在最外层的App传入store={store},在App文件引入connect(高阶组件)(可以接收四个参数:mapStateToProps(读取),mapDispatchToProps(修改)))
redux和context的区别
-
如果项目体量较小,只是需要一个公共的store存储state,而不讲究使用action来管理state,那context完全可以胜任。反之,则是redux的优点。
-
context的缺点:因为没有了action,state的值都是被直接修改,state的数据安全性不及redux。同时也不能使用redux的中间件,比如thunk/saga,在一些异步的情况需要自己来处理。
-
redux是中大型的状态管理场景,意味着状态规模很大,更新状态的逻辑代码比较复杂,存在多人协作。context适合小中型的状态管理场景,意味着状态规模不大,更新状态的逻辑代码不复杂
-
context不支持局部订阅,意味着,只要引用类型中的单个属性/成员发生变化,其余没有发生变化的成员也要全部render。context状态管理太分散。
-
redux还支持各种中间件的级联和一步业务的处理
context传值过程
react提供了context上下文模式,为了解决props每层都需要传递的问题。集context提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传递的方法。
- 创建一个context对象 const MyContext=React.createContext(defaultValue)defaultValue为默认值
- 在父组件内传值,引入创建的context对象,通过context对象.Provider的value属性进行传值。(如果没有传值的话defaultvalue的值就会生效)(多个provide也可以嵌套使用,里层的会覆盖外层的数据)当provider的value值发生变化时,它内部的所有消费组件都会重新渲染
- 接值通过引入context对象。设置静态属性contextType=context对象。然后通过this.context进行读取。
refs
refs是组件实例对象中的一个属性,值是一个对象,只需给vdom添加ref属性即可生成ref的指向(生成真实DOM)指向该dom类似于原生js中通过id获取节点。
实现refs的形式
- 字符串形式的ref(不建议使用)
- react支持一个特殊的属性,你可以将这个属性加在任何通过render返回的组件中,这也就是说对render()返回的组件进行一个标记,可以方便的定位这个组件实例。这就是ref的作用
2. 回调函数形式的ref(字符串ref的改进)
- ref属性的值除了是一个字符串,还可以是一个函数,这个函数是一个回调函数,在组件初始化或更新时有react调用,这个回调函数会得到一个参数,即当前的节点对象,我们可以将得到的节点对象挂载到组件的实例对象上,在需要的地方进行调用。
- 关于回调ref中回调执行次数的问题:官方支出,如果ref毁掉次数事宜内联函数的方式定义的,在更新过程中它会被执行两次。第一次传参数null,然后第二次会传入参数DOM元素。这是因为在每次渲染时会创建一个新的函数实例。所以react清空旧的ref并且设置新的。为保证更新之后得到的是更新之后的dom所以会先将该回调函数的参数赋值为null。以此保证每次都将之前的清除掉了。通过ref的回调次数定义成class绑定函数的方式可以避免上述问题,但是大多数它是无关紧要的。
3. createRef()创建的ref
- 先创建一个ref。例如:divRef=React.createRef()一个方法只能存储一个节点
- 在节点添加回调函数的ref。例如:ref={this.divRef}
- 在回调函数中调用回调函数的ref对象的current属性来进行设置。例如:this.divRef.current.style.background='#f00
通过ref获取子组件的信息
4. forwardRef(高阶组件透传ref):forwardRef将函数包裹起来,他会给函数传递两个参数,一个是ref,另一方个是props。在父组件内引入useRef
父组件内
得到的方法
其中的vaildateFields()方法能得到所有表单项内容。
原理
- 首先在父组件中通过React.createRef的方式创建一个myRef
- 将myRef在子组件中通过指定ref={myRef}的形式传递下去
- 在子组件的构造方法中,会接受传递过来的ref作为实参,然后这个实参的ref可以复制子组件中的dom节点等
- 当父组件的myRef关联相应的dom节点之后,就可以通过current属性在父组件中得到子组件的dom节点。
何时使用refs
- 处理focus(获取节点)、文本选择或者媒体播放
- 触发强制动画
- 集成第三方DOM库
ref指向问题
ref所指向的节点可以是dom节点,也可以是类组件。但是 Ref 属性指向的节点不能是函数组件(无状态组件)
■因为我们通过 ref 获得的组件,包含了生命周期和 state ,因此 ref 所指向的组件不可以是函数组件。
受控组件
使react的state称为唯一数据源,渲染表单的react组件还控制着用户输入过程中表单发生的操作。(组件里的value值是通过state来进行改变的,这就是受控组件。)(一个value属性,值为this.state的其中一个状态,还有onChange事件。)
非受控组件(不受状态控制)
有时使用受控组件会很麻烦,因为你需要为数据变化的每种方式都编写事件处理函数,并通过一个react组件传递所有输入的state。当你将之前的代码库转换为react或将react应用程序与非react库集成时,这种情况可能会令人厌烦,所以可以使用非受控组件来实现输入表单的另一种方式。非受控组件,表单数据交由DOM节点来处理。通过使用ref来从DOM节点中获取表单数据。使用非受控组件往往可以减少代码量。快速编写代码。
受控组件和非受控组件的区别
- 受控组件:
- 受控组件依赖于状态
- 受控组件的修改会实时映射到状态值上,此时可以对输入的内容进行校验
- 受控组件只有继承React.Component才会有状态
- 受控组件必须要在表单上使用onChange事件来绑定对应的事件。
- 非受控组件:
- 非受控组件不受状态的控制
- 非受控组件通过ref获取数据就是相当于操作DOM
- 非受控组件可以很容易和第三方组件结合,更容易同时集成React和React代码
条件渲染
可以使用if判断
可以使用三目运算符(条件运算符)
可以用逻辑与或逻辑或方法
列表渲染
map()处理数组和对象数据
map()是数组的一个方法。它创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。map()里面的处理函数接收两个参数。分别是当前元素,和当前元素的索引
key的作用
key可以在DOM中某些元素被增加或删除的时候帮助react识别哪些元素发生了变化。因此应该给数组中的每个元素赋予一个确定的标识,在开发过程中,我们需要保证每个元素的key在其同级元素中具有唯一性。在diff算法中,react会借助元素的key值来判断该元素是新近创建的还是被移动而来的元素。从而减少不必要的元素重渲染。此外,react还需要借助key值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换的函数中key的重要性。
为什么不建议用序列号,索引值
一个元素的key最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用来自数据的id作为元素的key,当元素没有确定的id时。你可以使用它的序列号索引index作为key(若元素没有重排,该方法效果不错,如果重排会产生问题)
react旧的生命周期
- 实例化期
- constructor=》defaultProps
- componentWillMount:第一次渲染阶段在调用render方法前会被调用。该方法在整个组件生命周期只会被调用一次,所以可以利用该方法做一些组件内部的初始化工作。这个是在render方法用前可修改state的最后一次机会(不会导致组件重新渲染)
- render:JSX通过这里,解析成对应的虚拟DOM,渲染成最终效果
- componentDidMount:第一次渲染成功过后,组件对应的DOM 已经添加到页面后调用。这个阶段表示组件对应的DOM 已经存在,我们可以在这个时候做一些依赖DOM的操作或者其他的一些如请求数据和第三方库整合的操作。如果嵌套了子组件。子组件会比父组件优先渲染,所以这个时候可以获取子组件对应的DOM(refs)
关于React中数据获取一定要在componentDidMount里面调用的原因?
constructor中获取数据的话,如果时间太长,或者出错,组件就渲染不出来,整个页面都没法渲染了。constructor是做组件初始化工作的,并不是设计用作加载数据的。如果使用SSR(服务端渲染)componentWillMount会执行两次,一次在服务端,一次在客户端。而componentDidMount不会。React16之后用了Fiber架构,只有componentDidMount生命周期函数是确定被执行一次的,类似componentWillMount的生命周期钩子都有可能执行多次,所以不加以在这些生命周期中做有副作用的操作,比如请求数据之类。componentDidMount在整个执行周期内就执行一次。是在组件已经完全挂载到网页上才会调用被执行,所以可以保证数据的加载。此外,在这个方法中调用setState方法,会触发重新渲染,所以,官方设计这个方法就是用来加载外部数据用的,或处理其他的副作用代码。
- 存在期
- componentWillReceiveProps(np){}:当组件获取新属性的时候,第一次渲染不会调用。这个时候可以根据新的属性来修改组件状态。
- shouldComponentUpdate(np,ns){}:接收到新属性或者新状态的时候在render前会被调用。该方法让我们有机会决定是否重新渲染组件,如果返回false,那么不会重新渲染组件。是react性能优化非常重要的一环。组件接受新的state或者props时调用。我们可以设置在此前后两个props和state是否相同,如果相同则返回false,阻止更新,因为相同的属性状态一定会生成相同的dom树。这样就不需要创造新的dom树和旧的dom进行diff算法对比,节省大量性能,尤其在dom结构复杂的时候
- componentWillUpdate(){}:当组件确定要更新,在render之前调用。这个时候可以确定一定会更新组件,可以执行更新前的操作。方法中不能使用setState,会陷入死循环,setState的操作应该在componentWillReceiveProps(){}方法中调用
- render(){}
- componentDidUpdate(){}:更新被应用到DOM之后。这个方法在更新真实的DOM成功之后调用,当我们需要访问 真实的DOM时,这个方法就经常用到
- 销毁期
- componentWillUnmount(){}:每当组件使用完成,这个组件就必须从DOM中销毁,此时该方法就会被调用。当我们在组件中使用了setinterval,那我们就需要在这个方法中调用clearInterval。
react16新的生命周期
- 实例化阶段
- constructor:加载的时候调用一次,可以初始化state
- getDerivedStateFromProps(props,state):会在存在期也调用一次,把存在的属性更新到状态里面,代替componentWillReceiveProps和componentWillMount。
- render:创建虚拟dom,进行diff算法,更新dom树都在此进行
- componentDidMount:组件渲染之后调用,只调用一次
- 更新阶段
- getDerivedStateFromProps(props,state):代替componentWillReceiveProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate:代替componentWillUpdate
- componentDidUpdate:组件生成真实dom后调用
- 卸载阶段
- componentWillUNmount:组件卸载时触发。
- 捕获错误
- componentDidCatch(error,info):主要是远程获取服务器错误,error是控制台报出的错误。当子组件中抛出错误后,componentDidCatch就会触发,可以在这个方法里捕获错误、打印错误信息或上报错误等操作
react生命周期变更的原因
原来的生命周期在React16推出Fiber之后就不合适了,因为要开启async rendering,在render函数之前的所有函数,都有可能被执行多次,如果开发者开了async rendering,而且又在以上这些render前执行的生命周期方法做ajax请求的话,那ajax将无谓的多次调用,而且在componentWillMount里发起ajax,不管多快得到结果也赶不上首次render,而且componentWillMount,在服务端渲染也会被调用到。也就是用一个静态函数getDerivedStateFromProps来取代deprecate的几个生命周期函数,就是强制开发者在render之前只做无副作用的操作,而且能做的操作局限在根据props和state决定新的state。
fetch与ajax和axios的区别
- fetch:fetch是替代ajax的,但是没有使用XMLHTTPRequest对象。它的返回值是一个promise对象。会有网断了,或者跨域被拒绝才会返回reject。fetch默认不会从服务端发送或接收任何cookies。如果要强制发送cookies必须在配置中进行设置credentials[krɪˈdenʃ(ə)lz]:‘include’这个属性才可以
- ajax:采用XMLHTTPRequest对象来编写的,没有进一步封装写法相对来说复杂。使用http协议进行通信。
- axios:是基于promise封装好的一个框架。它从浏览器中创建XMLHTTPRequest。支持拦截请求,拦截后的数据发送给客户端和服务器,支持浏览器和node.js发送请求,前后端发请求,支持promise写法,支持自动解析json,支持中断请求,提供了一些并发请求的接口
react router路由
- 路由的原理
- react路由通过一种特殊的连接,点击时会修改浏览器地址栏中的地址,但是浏览器是会忽略掉这次变化,所有切换不会被跳转,但是路由会监听到地址path的变化从而根据不同的地址渲染出来不同的组件
- 路由的模式
- history模式:通过历史记录来保持UI和URL的一致。用的是
BrowserRouter - hash模式:通过哈希值#来保持UI和URL 的一致
HashRouter MemoryRouter:能在内存中保存URL的历史记录。没有对地址栏进行改写,更加美观StaticRouter:从不会改变地址,没有切换的功能。
- history模式:通过历史记录来保持UI和URL的一致。用的是
安装依赖
- cnpm i react-router-dom -D 适用于网页的
React-router-dom的组成部分
- 路由器组件
- 路由器匹配组件
- 导航组件
- 路由器组件:
Router实际上不直接使用该组件,而是它衍生出来的组件。包裹URL对应的根组件即可。BrowserRouter使用h5提供的historyAPI来保持UI与URL的同步。HashRouter使用URL的hash来保持UI和URL的同步。MemoryRouter通过在内存保存URL的历史记录,不会修改地址栏。StaticRouter从不会改变地址,不能实现切换。 - 路由器匹配组件:
Route组件主要的作用就是当一个location匹配路由的path时,渲染某些UI。多个Route由Routes包裹起来,作用是第一次URL和path匹配上之后就不在向下匹配。
Route组件有如下属性:- path:路径匹配路径
- element:设置要显示的组件。
- index:设置默认渲染的路由组件。
- 导航组件:
Link组件用来处理a链接类似的功能。(它会在页面中生成一个a标签)react-router-dom拦截了实际a标签的默认动作,然后根据所有使用的路由模式(Hash或者history)来进行处理,改变了URL,但不会发生请求。同时根据Route中的设置把对应的组件显示在指定位置- 属性:to:要跳转的路径或地址
- replace:为true时,点击链接后将使用新地址替换掉访问历史记录里面的原地址,为false时,点击链接后将在原有访问历史记录的基础上加一个新的记录。默认为false
- 导航组件的特定版本
NavLink。会在匹配上当前URL的时候给已经渲染的元素加样式。- 组件属性
- activeClassName:设置选中样式,默认值为active(需要自己单独设置样式类)activeStyle(object)当元素被选中时,为此元素该属性值当中设置的样式
- 组件属性
路由嵌套
方法一:跟路由页面可以直接加*,能够匹配所有,然后在一级路由的页面内写二级路由
方法二:在一级路由Route标签内写二级路由,可以省略子路由的路径,直接写路由名称即可,子页面用import引入Outlet,然后在需要的地方进行调用即可。
路由传参三种方式
- params:在路由匹配组件Route的path属性的路径后添加:自定义名字,在导航组件Link的to属性后添加要传递的数据,然后通过this.props.match.params.自定义名称获取参数
优势:刷新地址栏,参数依然存在
缺点:只能传字符串。并且,如果传的值太多的话,URl会变得长而且丑
2. query方法:通过this.props.history.push({pathname:'/xxx',"query"{"name":"tom","age":20}跳转路由并传值,Link to=({"pathname:'./query',query:{'name':'tom'})随后通过this.props.location.query.xxx获取参数
优势:传参优雅,传递参数可传对象
缺点:刷新地址栏,参数丢失
3. state传值
Route path=‘/sort’ element={<Sort/>}``Link to=("path:'/sort',state:{'name':'sunny'})通过this.props.histroy.push("pathname":"/sort",state:{name:"sunny"})进行跳转。通过this.props.location.state.name进行读取。
优缺点同query
4. search
优缺点同params
函数式组件和类式组件的区别
- 函数式组件不能访问this对象
- 函数式组件无法访问生命周期的方法
- 函数式组件无法访问state状态,只能访问输入props,同样的props会得到同样的渲染结果,不会有副作用
函数式组件为什么不能访问this?
函数有react调用,调用过程中,babel转换器将JSX转换成js,这时函数会自动开启严格模式,严格模式下规定全局模式下自定义的函数中的this是不能指向window,所以this的结果是undefined。
函数式组件语法
Hooks
React Hooks是React16.8引入的新特性,允许我们在不使用class的前提下使用state和其他特性。即在函数式组件中使用类式组件的特性
Hooks的优势
- Hooks出现之前,组件之间复用状态逻辑很难,解决方案(HOC(高阶组件,函数里返回的值是一个组件)、Render Props)都需要重新组织组件结构,且代码难以理解。在React DevTools中观察过React应用,你会发现有providers、consumers,高阶组件,render props等其它抽象层组成的组件会形成“嵌套地狱”。
- 组件维护越来越复杂,比如事件监听逻辑要在不同的生命周期中绑定和解绑,复杂的页面componentDidMount包含很多逻辑,代码阅读性变得很差。
- class组件中的this难以理解,且class不能很好的压缩,并且会使热重载出来不稳定的情况。
以上情况Hooks都能解决,所以它的优势是: - 能避免地狱式嵌套,可读性提高
- 函数式组件,比class更容易理解
- class组件生命周期太多太复杂,Hooks使函数功能齐全且简单。
- 解决HOC和Render Props的缺点
- UI和逻辑更容易分离(更简洁)
Hooks的原理
Hooks的原理:涉及到状态的hook都需要定义两个参数,即修改状态的本身和修改状态的方法,在组件节点中,他们都是以数组的结构存储,每次的修改实际上是向结构中添加内容并且修改指针的指向。
Hooks的规则
- 只在最顶层使用Hooks:不要再循环、条件或嵌套函数中调用Hooks,确保总是字啊你的React的最顶层调用他们。
- 只在React函数式组件调用Hook(普通的函数里面不能用,class类式组件不可以使用)
常用的Hooks
- useSate:
- 用法:先在顶层引入useState。传入一个参数作为useState的初始值,返回值是当前state以及更新state的函数,利用数组的结构赋值,设置状态的名字,和设置状态的方法,该方法用来修改该状态。
- 为什么要使用数组而不是对象?如果useState返回的是数组,那么使用者可以对数组中的元素命名,代码看起来也比较干净。如果useState返回的是对象,在结构对象的时候必须要和useState内部实现返回的对象同名,想要使用多次的话,必须设置别名才能使用返回值。返回对象的使用方法比较麻烦,为了降低使用的复杂度。而且返回数组的话可以直接根据顺序结构。所以使用的是数组
- 原理:useState方法会返回当前状态和设置状态的方法,每当状态改变之后,方法中会调用刷新视图的render方法。状态我们需要放在最外面,方便下次执行函数时可以重新取值。初始状态只会在函数第一次执行的时候赋值。当多个状态存在的时候,我们需要使用数组保存状态
- 读取state:
- 在类式组件中通过this.state.count进行读取
- 在函数式组件中可以直接状态名进行读取
- 更新state
- 在类式组件中需要调用this.setState来更新状态
- 在函数式组件中通过useState数组结构的set方法来更新状态
2. useEffect:可以在函数组件中执行副作用操作。也可以用来侦听,把要侦听的属性作为依赖项即可。可以把UseEffect看做componentDidMount,componentDidUpdate和componentWillUNmount这三个函数的组合。也就是在组件加载以后要做的操作。页面可以写多个useEffect。
在React组件中有两种常见的副作用操作:需要清除的和不需要清除的,(需要清除的比如计时器、闭包,使用完毕后需要清除)
- 用法:
- 第一个参数,接收一个函数作为参数
- 第二个参数,接收【依赖列表】,只有依赖更新时,才会执行函数
- 返回一个函数,先执行返回函数,再执行参数函数
- 不接受第二个参数的情况下:第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数
- 接受第二个参数的情况下:
- 传入的为空数组[],那么useEffect不依赖于state、props中的任意值,useEffect就只会运行一次。
- 传入一个值构建的数据,或者多个值构建的数组,如:[num]/[num,val]当其中的值发生改变时就会触发回调函数
- 清除副作用
- 未清除副作用的话,有可能会造成页面性能。内存泄漏等问题
- 需要清理的直接添加return 返回的是一个函数,在函数内直接清理即可
-
React何时清除effect?
- React会在组件卸载的时候执行清除操作。effect在每次渲染的时候都会执行。
-
什么是副作用?
- 副作用 ( side effect ): 数据获取,数据订阅,以及手动更改 React 组件中的 DOM 都属于副作用
- 因为我们渲染出的页面都是静态的,任何在其之后的操作都会对他产生影响,所以称之为副作用 3.useMemo:使用useMemo可以传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值。简单来说,作用就是让组件中的函数跟随状态更新,即优化函数组件中的功能函数
-
使用:
- 接收一个函数作为参数
- 同样接收第二个参数作为以来列表
- 返回的是一个值。返回值可以是任何,函数、对象等都可以
4. useCallback:主要作用优化代码。
- 把内联回调函数及依赖项数组作为参数传入,useCallback它将返回该回调函数的缓存版本,该回调函数仅在某个依赖项改变时才会更新。
useCallback与useMemo的区别
useCallback用于缓存回调函数,useMemo用于缓存值。
useCallback适用场景
可以对父子组件传参渲染的问题进行优化。简单来说就是,父组件的传入函数不更新,就不会触发子组件的函数重新执行
小结
- 在子组件不需要父组件的值和函数的情况下,只需要使用memo函数包裹子组件即可
- 如果有函数传递给子组件,使用useCallback
- 缓存一个组件内的复杂计算逻辑需要返回值时,使用useMemo
- 如果有值传递给子组件,使用useMemo
- useRef:useRef返回一个可变的ref对象,其current属性被初始化为传入的参数。
- 特点
- 返回的ref对象在组件的整个生命周期内保持不变
- 修改ref.current不会引发组件重新渲染
- 利用这些特征我们除了可以将dom存储在current以外还可以将哪些不涉及到组件更新以及在函数调用过程中不应该被重复初始化的数据存储在其中,就像类式组件中在实例对象上存储的数据一样
6. useContext:是让子组件之间共享父组件传入的状态的。
- 用法:接收一个context对象(React.createContext的返回值)并返回该context的当前值。当前的context值由上层组件中距离当前组件最近的
mycontext.Provider的value props决定
当组件上层最近的MyContext.Provider更新时,该Hook会触发重渲染,并使用最新传递给MyContext Provider的context value值。即使祖先使用React.memo或shouldComponentUpdate,也会在组件本身使用useContext时重新渲染
useContext的参数必须是context对象本身useContext(MyContext)
父组件(也可以把React.createContext写成一个公共组件,使用时进行调用):
子组件:
7. useReducer:可以替代useSate。它接收一个形如(state,action)=>newState的reducer,并返回当前的state以及与其配套的dispatch方法。
在某些场景下,useReducer会比useState更实用,例如state逻辑较复杂且包含多个子值,或者下一个state依赖于之前的state等。并且,使用useReducer还能给那些会触发深更新的组件做性能优化,因为可以向子组件传递dispatch而不是回调函数。
redux
redux是专门做状态管理的js库。可以用在react、angular。vue等任何项目上,是一个js插件。
- 作用:以集中式store的方式对整个应用中使用的状态进行集中管理。其规则确保状态只能以可预测的方式更新
- 什么情况下使用redux:某个组件的状态,需要让其他组件可以随时拿到,要共享自己的状态(一般在大型的项目中使用)
- 写法
- 写store文件
- 先引入createStore这个对象,再引入reducers。
- 创建一个变量=createStore(第一个参数是引入的reducers,【第二个参数是默认值】,第三个参数更改异步模式,【可选参数】) 然后export导出
- 写store文件
- 在组件内引用store,引入action里的对象。useSate中通过store.getState方法作为组件状态的初始值。
- 当数据发生改变时,在组件内部通过store.dispatch方法分发action。dispatch方法调用action对象传入新数据,作为action的payload属性值
- action有type属性,用来判断操作行为,payload属性值为组件传来的新数据,发送给store,由store发送给reducers
- reducers接收两个参数,一个是state,一个是action。通过Switch方法判断action的type属性,通过type属性来进行具体的操作,返回的数据作为store新的state。
- store的state发生改变,在组件内通过store.subscribe方法监听状态。subscribe参数是一个函数。可以通过store.getState方法拿到新的数据,然后设置成组件的新状态,使组件进行更新。
App页面
action页面
store页面
reducers页面
异步action和中间件
使用中间件。就是对store.dispatch()的增强,变成异步操作
dispatch一个action之后,到达reducers之前,进行一些额外的操作,就需要用到middleware,可以利用Reduxmiddleware来进行日志记录。创建崩溃报告、调用异步接口或者路由等等。
redux-thunk(类似于回调函数进行封装的ajax)
通过多参数的柯里化函数(高阶函数)以实现对函数的惰性求值,从而将同步的action转为异步的action。
- 先引用applyMiddleware这个方法,作为store的第三个参数。引入thunk。作为applyMiddleware的参数
- 组件内调用store.dispatch。dispatch接收一个函数作为形参,该函数的返回值是一个函数。该函数接收两个参数,真正的dispatch和原来的state,然后做异步操作,操作的结果作为dispatch分发action得到的新数据。这样似的dispatch在达到reducers之前进行了异步操作,加强了dispatch的功能
redux-promise(类似于用promise封装的ajax)
不同的中间件都有着自己的适用场景。react-thunk比较适于简单的API请求的场景,而promise则更适用于输入输出操作,通过判断返回的promise的状态返回新的action。
- 先引用applymiddleware
- 在引用promise
- appleMiddleware作为store的第三个参数。它的参数是promise
- 在组件内调用store.dispatch()参数为一个函数。
- 该函数通过async/await得到一个promise对象。对象作为dispatch分发action的新数据
redux-thunk的原理
redux-thunk中间件可以让action创建函数先不返回一个action对象,而是返回一个函数,函数传递两个参数(dispatch,getState)在函数体内进行业务逻辑的封装。
- 安装:cnpm i redux-thunk -D
- 导入 import thunk from “redux-thunk”
- 导入中间件: import {createStore,appMiddleware} from “redux”
- 创建store: let store = createStore(reducers,[],applyMiddleware(thunk))
- applyMiddleware它是redux的原生方法,作用是将所有中间件组成一个数组,依次执行。
react-redux
- react-redux库将react和redux结合在一起,对redux api的进行封装形成基于react的组件,操作更加简洁
- 安装: cnpm i redux -D(必须先安装redux) cnpm i react-redux -D
- react-redux原理:主要功能就是代替
事件订阅subscribe,监听store变化更新状态 - react-redux的两个最主要的功能
- connect:连接数据处理组件和内部UI组件(高阶组件)
- Provider:提供包含store的context,把我们用redux创建的store传递到内部的其他组件、让内部组件可以享有这个store并提供对state的更新。
- 用法
- 在index文件内引入store和Provider
- 用Provider包裹住App文件,设置store属性传递store
- 在App文件内引入connect和action暴露出来的对象,connect是一个高阶组件,返回值是App组件。connect接受两个参数maptostateprops作用是读取store的状态。接受的参数是state。state就是store里的state,返回值必须是一个对象。第二个参数是mapdispatchtoprops。mapdispatchtoprops接受参数dispatch,用于分发action。
函数式组件,其余的没有改变,主要是App文件,引用了useSelector和useDispatch。useSelector用于接收store的state。useDispatch用于分发action
useSelect接收一个函数,该函数的参数是store的state,从而可以得到store里的状态
调用useDispatch方法赋值给一个变量,然后直接通过该变量分发action即可
高阶组件
- 什么是高阶组件?
- 高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
- 高阶组件HOC是react中对组件逻辑进行重用的高级技术。但高阶组件本身并不是React API,他只是一种模式,这种模式是有react自身的组合性质必然产生的。