- 不是每个都很常用,但用到的时候必须要知道
- 考察候选人对React的掌握是否全面,且有深度
- 考察做过的项目是否有深度和复杂度(至少能用到高级特性)
函数组件
- 函数组件是 纯函数,输入props,输出JSX
- 没有实例(自然也没有this),没有生命周期函数与state,所以性能好
- 不能扩展其他方法
- 函数组件,即 无状态组件,更多知识可参考“Redux进阶(1)UI组件、容器组件、无状态组件”
- 当一个组件中只有render函数时,我们可以使用 无状态组件 替换 它。
- 使用方法:使用常量const定义一个箭头函数,该函数接收一个参数props(来自父组件的变量、方法),返回原本render函数中的内容(记得输出)
ref属性
- 作用:获取DOM元素,具体例子在下方“非受控组件”中
- ref可参考“React高级内容(2)”
- ref属性值 必须是通过
React.create()
创建的对象 - 三个地方名字(refName)一致才能获取DOM节点
- 创建ref:
this.refName = React.createRef()
(构造函数中) - 使用ref:
<input defaultValue={this.state.name} ref={this.refName}/>
(render函数中) - 通过 ref 获取 DOM 节点(elem):
const elem = this.refName.current
(事件函数中)
- 创建ref:
非受控组件
- 非受控组件:组件值不受state控制(比如:input的值不受state控制),只是使用state给组件赋予了初始值(比如:input的defaultValue属性),由于赋予初始值以后组件值就和state无关了,所以后面想获取 组件值 就只能 通过ref获取DOM元素 的值的方式来操作了。(可回顾“React知识点汇总(基础使用1)”中的 受控组件(value/checked))
- 使用场景:由于非受控组件需要搭配ref去操作DOM来获取值,所以按照react设计初衷是不推荐使用的,但是特殊时候只能使用 非受控组件,比如:
- 必须手动操作DOM元素,setState实现不了的时候
- 文件上传
<input type=file>
(可参考MDN input file使用方法,可以看到想要获取上传的文件的的 File对象 是需要 操作DOM元素的) - 某些富文本编辑器,需要传入DOM元素(做加粗、加下划线等操作 )
- 受控组件vs非受控组件:优先使用受控组件,这样符合React设计原则。必须操作DOM时,再使用非受控组件。
input的 defaultValue属性
- 注意:区别react中input的 defaultValue属性 和 value属性
- react中input的defaultValue就相当于原生DOM中的value属性,这样写出来的组件,其value值就是用户输入的内容,和react没有关系,完全不管输入的过程。
- 而react中input的value属性必须使用onChange来监听这个input,使state和input关联(即 受控组件)
- 如果在使用input时只需要获取model里的值时,使用 defaultValue就可以了。如果需要获取model的值并且还需要改变它的时候,就需要使用value结合onChange事件与state联系就可以了。(但还是尽量使用value,毕竟defaultValue需要结合ref来获取DOM元素,而react的设计初衷就是尽量不要操作DOM,通过state来带动DOM)
- 例子:
checkbox的defaultChecked属性
上传文件【必须使用非受控组件】
- 可参考MDN input file使用方法,可以看到想要获取上传的文件的的 File对象 是需要 操作DOM元素的,所以就必须使用非受控组件。
Protals(传送门 让组件渲染到父组件以外)
- 组件默认会按照既定层次嵌套渲染,但我们可以使用Protals让组件渲染到父组件以外。
- 在react中的组件排布位置是不变的(不会改变组件解构),只是在渲染的时候做了一个hack使相关组件渲染到我们指定的地方。
- Portals使用场景:
- 父组件是BFC时,会限制子组件的布局,此时可使用Protals将子组件脱离BFC父组件(比如
overflow:hidden
) - 父组件z-index值太小时,将父组件往外放
- fixed元素需要放在body第一层级(DOM树最外层),有更好的浏览器兼容性
- 父组件是BFC时,会限制子组件的布局,此时可使用Protals将子组件脱离BFC父组件(比如
- 总之,Protals一般在CSS布局方面使用
例子(fixed元素放在body第一层级)
- 正常渲染时:(注意:
this.props.children
可获取的内容)可以看到固定定位的元素位于DOM树内层 - 使用 Portals 渲染到 body 上:
context(传递公共信息)
- 公共信息(语言、主题)如何传递给每个组件?用props太繁琐,用redux小题大做,使用context将 公共信息(语言、主题)如何传递给每个组件。
- 例子:在这里只是假设theme为需要使用的主题,在这个例子中,从外向内是App组件(最外层组件)=》Toolbar组件(中间组件)=》ThemedButton组件(class 组件)、ThemeLink组件(函数组件)
- 父组件做context生产方,创建context,并使用context包裹子组件给子组件获取context的能力:
- 中间的组件不需要指明往下传递 theme :
- 子组件做context消费方,可以获取到父组件中传递的context。
- class子组件 直接使用
this.context
可获取父组件设置的context: - 函数子组件 由于没有实例导致没有this,所以:
- class子组件 直接使用
1 | import React from 'react' |
异步组件(懒加载)
- 使用场景:组件比较大 或者需要 懒加载 时,我们可以使用 异步组件来做(性能优化)
- vue中可用import(),react中可用React.lazy+React.Suspense(见下方例子)或者使用react-loadable模块
- 例子:
- 使用React.lazy+React.Suspense实现异步组件:
- 使用react-loadable模块 实现异步组件 的例子
【重点】性能优化
- 比起vue,性能优化对于React更加重要。因为react本身设计的shouldComponentUpdate默认是返回true的,也就是说父组件一更新就会带着所有子组件重新渲染,不管子组件是否有数据变化(是否需要重新渲染)。
- 回顾讲setState时重点强调的不可变值
- 措施:
- 通过shouldComponentUpdate()设置所接受的参数(新的Props和State值)来判断何时返回false,避免不必要的子组件渲染(注意**一定要搭配state不可变值,原因见下**)
- state数据设计为浅层(不要出现二维数组之类的),避免深拷贝或者深度比较(他们需要一次性递归,很消耗性能)
- class组件 使用 PureComponent ,它自带浅比较,不需要我们手写shouldComponentUpdate()【需要将state数据设计为浅层】
- 前提条件:必须搭配state不可变值,即必须使用immutable管理数据才能使用PureComponent,否则坑很多
- 函数组件 使用 React.memo,可用 自定义函数 代替shouldComponentUpdate(),作为参数2传入React.memo(),解决函数组件没有生命周期函数的问题。通过React.memo将返回一个新的函数组件,它自带浅比较决定是否重新渲染
- immutable.js可生成不可变的对象,它基于共享数据(不是深拷贝),速度快性能好,但有一定学习和迁移成本,按需使用。(补充:redux-immutable库的combineReducers()可使合成各个组件的reducer得到的就是immutable对象)
- 不是任何时候都需要使用性能优化,不卡顿可不用
shouldComponentUpdate(简称SCU)
- React默认机制:父组件有更新,(不管子组件是否有改变)子组件也无条件也更新!!!所以我们需要优化react,避免不必要重复渲染的子组件被带着渲染来影响性能。
- 如不设置shouldComponentUpdate 则默认返回true,代表可以渲染:
- 使用shouldComponentUpdate做 性能优化:shouldComponentUpdate()会接收新的Props和State值作为参数,并且给了我们权利去决定什么时候返回false来拒绝重复渲染
- SCU优化一定要每次都用吗?其实不是,需要的时候才优化,如果页面不卡顿就可以不用,一切以开发为主
SCU一定要配合state不可变值
- 原因:如果state不是不可变值,即它在setState前被改变了,则SCU中拿到的nextState和state是相同的,导致该重新渲染时被判断为不可重新渲染。
(lodash库的isEqual可进行深度比较,对于引用类型,**===
是会比较地址的**,而 lodash库的isEqual 则只是比较内容物是否相等) - 例子:
- 总结:所以SCU一定要配合state不可变值才能保证不会出错
- 注意:深度比较 很耗费性能,需要进行一次性递归到底,设计state时设计的浅一些来避免使用深度比较会好一些(React的PureComponent和 React.memo就实现了自带浅比较来决定是否重新渲染)
PureComponent 和 React.memo
- React的PureComponent(纯组件)实现了自带浅比较,它只对比state和props数据的第一层,比起 深度比较 性能更好。
- 注意:PureComponent也必须要搭配state不可变值(即不能改变state本身),否则坑很多
- 让 class组件 继承PureComponent,不需要手写SCU,它自带SCU并实现了浅比较(PureComponent的例子)
- memo,即 函数组件 中的PureComponent:可用 自定义函数 代替shouldComponentUpdate(),解决函数组件没有生命周期函数的问题
- 浅比较已适用大部分情况(尽量不要做深度比较),这就需要我们在react中设计state时尽量扁平,不要一层套一层
不可变值immutable.js
- immutable.js彻底拥抱“不可变值”,它基于共享数据(不是深拷贝),速度快性能好,但有一定学习和迁移成本,按需使用。
- 深拷贝的性能差
- 具体使用可参考博客“简书Header组件开发(使用immutable.js库、redux-immutable库)”
- 例子:
按需使用 & state层级
- 不是任何时候都需要使用性能优化,不卡顿可不用
- state层级设计的浅一些可避免深拷贝/深度比较等耗费性能的操作
组件公共逻辑的抽离
- mixin已被React弃用
- 高阶组件HOC Vs Render Props 按需使用即可
- 高阶组件HOC:模式简单,但会增加组件层级(需要考虑透传props以及是否会被覆盖的问题)
- 总结:在高阶组件函数内自定义公共逻辑(包含逻辑的class组件B 包含 负责渲染的原函数组件A),高阶组件函数 接受 负责渲染的原组件A(一般是函数组件),返回 涵盖逻辑的新组件B(class组件)
- 注意:在高阶组件函数内引用原组件A时需要透传所有props,以此保证别的组件调用新的高阶组件B时传给B的属性/方法 可被原组件A调用
- Render Props:代码简洁,学习成本较高
- 总结:(原组件A 包含 负责逻辑的class组件B 包含 负责渲染的函数组件C。)可复用的是自定义的class组件B,它返回函数组件C。原组件A(一般是函数组件)通过调用 class组件B 并向 子组件B 传递 属性(函数组件) 的方式定义需要渲染的 函数组件C
- 高阶组件HOC:模式简单,但会增加组件层级(需要考虑透传props以及是否会被覆盖的问题)
高阶组件HOC
- 高阶组件不是一种功能,是一种模式,他是一个函数,这个函数接受一个组件并返回一个新组件
- 高阶组件负责公共逻辑,传入的组件 传出后 生成的新组件 就会带上这部分逻辑
- 定义高阶组件时 需要向 传入的组件 传递自己的所有props(包括别人传过来的),这样传入的组件才能获取使用(详情见下面例子)
- 高阶组件的定义:创建一个函数,接受一个组件A,在函数内创建组件B将组件A包裹并给他逻辑,最后返回组件B。
例子
- 实现效果:鼠标划过时显示鼠标位置
- 思路:假设很多组件都要使用这个逻辑,那么怎么将这个逻辑抽离出来?在 高阶组件 的函数中实现鼠标滑动的逻辑,App组件(函数组件)只需要负责渲染,最终返回的是 App组件通过高阶组件来返回的新组件(已经包含了逻辑的App组件)
- 代码:
react-redux connect
react-redux connect也是高阶组件
connect(函数1,函数2)
返回的是高阶组件的函数(回顾:redux-thunk中间件使action可以为函数)- 在调用1返回的函数并将组件TodoList传入,得到的就是新的组件
render props
- 高阶组件 是高阶组件(负责逻辑)包含组件A(负责渲染)(即 逻辑包含渲染);render props 是 组件A包含class组件,class组件(负责逻辑)中包含函数组件(负责渲染(即 逻辑包含渲染)。
- 区分 高阶组件 和 render props 的包裹关系:
- 思路:
- 左图定义一个class组件Factory,在class组件中将state作为参数传给 函数(组件)render(注意:class组件需要设置propTypes来规定必须接收一个 render 属性,而且是函数(具体见下方例子))
- 右图是调用class组件Factory时,在render属性上定义一个函数组件render,该组件通过参数获取到父组件Factory中传过来的数据(state)
- 思路:
- 例子:
- 实现效果:鼠标划过时显示鼠标位置
- 代码:获取App的父组件的值的方法比使用 高阶组件 要简单,毕竟App组件就是最外层,不需要透传所有props。