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-renderfunction 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 自更新组件
抽离出自更新组件到祖父级(最好存放在的组件中)
