减少组件的渲染次数,能提升 React App 的运行时性能。通过写法的优化,可以减少不必要的组件渲染次数。

React 系统的性能基本上是靠缓存撑着的,缓存一破差不多整个楼都可以塌了。因此优化写法,主要靠缓存。

优化写法

1. 组件 Render 时,避免 state, props 没变的子组件 Render

组件 Render 会导致的其子组件 Render,即使子组件的 state, props 没变。

子组件用 PureComponent 和 React.memo 可以避免这种情况下的 Render。类组件用 PureComponent,函数组件用React.memo。示例:

  1. // 类组件
  2. class class ClassComp extends React.PureComponent{}
  3. // 函数组件
  4. function FnComp () {}
  5. React.memo(FnComp)

2. 函数组件 Render 时,避免变化的函数属性值,导致子组件 Render

函数组件中的函数,每运行一次,都会生成一个新的函数。如果这个函数是某个子组件的属性,函数 Render 一次,都会导致子组件的 Render。

用 useCallback 包裹函数,可以避免这种情况下不必要的 Render。

  1. const handleClick = useCallback(() => ..., [])
  2. return (
  3. <ChildComp onClick={handleClick}>
  4. )

3. 组件 Render 时,属性值避免用箭头函数值,导致子组件 Render

如果子组件的属性值是个箭头函数,父组件每次 Render,箭头函数都是新的,会导致子组件的 Render。

属性值用实例方法,就能避免这种情况。例如:

  1. handleClick = () => {...},
  2. render() {
  3. return (
  4. <ChildComp onClick={handleClick}>
  5. )
  6. }

Render Props 也出现这样的问题。如:

  1. <Mouse>
  2. {mouse => (
  3. <ChildComp pos={mouse}>
  4. )}
  5. </Mouse>

解决方案也是将其改成实例方法:

  1. <Mouse>
  2. {this.renderChild}
  3. </Mouse>

4. 避免 Prop Drilling 导致的中间组件的 Render

Prop drilling 指将外层组件的 state 通过 props 一层层传下去,传递到层级很深的子组件的过程。外层组件的 state 发生变化,中间组件都会 Render。

层级很深的子组件可以直接取到值,不需要中间属性的传递,就能避免中间属性的 Render。用 Context API 或 Redux,MobX 等状态管理工具可以让子组件直接取到值。用 Context API 的示例:

  1. // 父组件提供数据
  2. <ThemeContext.Provider value={{ theme: this.state.theme }}>
  3. <Comp1>
  4. <Comp2>
  5. <Comp3>
  6. <ThemeContext.Consumer>
  7. {({theme}) => {
  8. // 子组件拿值
  9. }}
  10. </ThemeContext.Consumer>
  11. </Comp3>
  12. </Comp2>
  13. </Comp1>
  14. </ThemeContext.Provider>

5. 如何避免 useContext 重新渲染

注意: 当 Provider 的组件重新渲染时(即使 value 值没有变化),它内部的所有消费组件都会重新渲染(使用 useContext 的组件)。即使:

  • 消费组件用 React.memo, 并且该组件的属性值没有变。
  • 消费组件的父组件用 useMemo 包裹子组件。


    解决方案:

  1. 关注在应用中使用的Context的顺序,让不变的在外层,多变的在内层。
  2. Context中的内容可以按使用场景和变与不变来拆分成多个更细粒度匠,以减少渲染。
  3. 消费组件中不需要重新渲染部分,用 useMemo 包裹。
  4. 发布订阅模式。第三方库:react-tracked

参考:

6. 避免 redux 的不必要的组件渲染

redux 中的状态值变化了,所有用 redux 的组件都会重新渲染,造成不必要的组件渲染。

解决方案:

  1. 用 redux 的 useSelector 来筛选需要的 state。useSelector 拥有多级缓存,确保用到数据的更新时,组件才会重新渲染。
  2. 可以考虑用 MobX。“所有对数据的变更和使用都会在运行时被追踪到,并构成一个截取所有状态和输出之间关系的依赖树。这样保证了那些依赖于状态的计算只有在真正需要时才会运行,就像 React 组件一样。无需使用记忆化或选择器之类容易出错的次优技巧来对组件进行手动优化。(All changes to and uses of your data are tracked at runtime, building a dependency tree that captures all relations between state and output. This guarantees that computations depending on your state, like React components, run only when strictly needed. There is no need to manually optimize components with error-prone and sub-optimal techniques like memoization and selectors.)”
  3. 可以考虑状态管理库:Recoil。它的最大优势是精准的只触发渲染组件的更新。

    参考文档