1. 前置说明
1.1 React的思维模式
view = fn(props, state, context)
- 只要参数发生改变,视图一定发生
re-render
- 没有被子组件使用的
props
会被丢弃,比如传递了style
,但是子组件没有操作style
属性,或者没有restProps
,则style
无效
1.2 React的变更
默认是指【指针的变更】,不会监听对象的属性变更
- 采用
Object.is(valA, valB)
进行比较 -
useState
产生的值,需要setState()
会触发re-render
,和上一次一致时,不触发re-render
- 可以采用
immutability-helper
对复杂对象进行处理
- 可以采用
useMemo
,监听变量未改变,则产生的值也不会变
1.3 React父组件re-render的影响
若父组件发生了re-render
,则子组件也会re-render
- 使用JSX时,会转成
React.createElement
-
React.memo
的使用 - 封装
Provider
组件,子组件采用<Context.Provider value={{theme: this.state.theme, switchTheme: this.switchTheme}}> {this.props.children} </Context.Provider>
1.4 参考的轮子
2. 已知会导致re-render的可能性
- 组件
props
变更() - 组件监听的
dva
变更 - 组件监听的
context
变更 - 组件
eventBus
接受到消息 - 组件监听的
state
,用户会频繁操作,或一次操作,引发了多次setState,且不在一个effect中 - 父组件发生
re-render
- 父组件包含自更新组件,比如
key
会变化
3. 解决方案
3.1 props
的判定
React中将【属性】【函数】【children】,统一传递给props
属性(React.memo(组件名, (prev, next) => {})
)+ 回调函数(useCallback
、useMemoziedFn
)
- 对于【基本数据类型】而言,直接使用
React.memo
包裹即可 - 对于【复杂数据类型】而言,需要传递第二个参数进行判定,函数返回
false
会进行re-render
function areEqual(prevProps, nextProps) { /* 如果把 nextProps 传入 render 方法的返回结果与 将 prevProps 传入 render 方法的返回结果一致则返回 true, 否则返回 false */ } export default React.memo(MyComponent, areEqual);
- 深度比较
prev
和next
的值是否相等 -
lodash
的isEqual
或react-fast-compare
的isEqual
- 深度比较
- 对于【回调函数】而言,需要保证函数本身的指针不变(官方推荐采用
useCallback
)。但是若回调函数依赖外部变量,会使得指针发生改变,则失效;若不监听变量,则会产生问题,也失效。这时useMemoizedFn
就有作用了
3.2 useSelector
的使用
-
不要返回大对象
反例:const app = useSelector((state: { app: any }) => state.app) || {};
- 利用第二个参数进行深度比较,来减少
re-render
3.3 useContext
的使用
- 存储的属性颗粒度要小,不要直接存储大对象
- 封装Provider
3.4 eventBus的使用
注销时,off消费函数
useEffect(() => {
EventEmitter.on('refreshTagList', refreshCbk);
return () => {
EventEmitter.off('refreshTagList', refreshCbk);
};
}, []);
3.5 本组件优化策略
-
usememo
(降低非必要渲染) -
useDebounceFn
(防抖) - 多个
setState
合并 -
useUpdateEffect
(减少初始化的不必要数据变更)
3.6 父组件re-render
同3.1策略
3.7 自更新组件
抽离出自更新组件到祖父级(最好存放在的组件中)