React 应用也是前端应用,如果之前你知道一些前端项目普适的性能优化手段,比如资源加载过程中的优化、减少重绘与回流、服务端渲染、启用 CDN 等,那么这些手段对于 React 来说也是同样奏效的。
不过对于 React 项目来说,它有一个区别于传统前端项目的重要特点,就是以 React 组件的形式来组织逻辑:组件允许我们将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。因此,除了前面所提到的普适的前端性能优化手段之外,React 还有一些充满了自身特色的性能优化思路,这些思路基本都围绕“组件性能优化”这个中心思想展开
-
使用 shouldComponentUpdate 规避冗余的更新逻辑
-
PureComponent + Immutable.js
-
React.memo 与 useMemo
1、shouldComponentUpdate
shouldComponentUpdate 是 React 类组件的一个生命周期
shouldComponentUpdate(nextProps, nextState)
render 方法由于伴随着对虚拟 DOM 的构建和对比,过程可以说相当耗时。而在 React 当中,很多时候我们会不经意间就频繁地调用了 render。为了避免不必要的 render 操作带来的性能开销,React 提供了 shouldComponentUpdate 这个口子。React 组件会根据 shouldComponentUpdate 的返回值,来决定是否执行该方法之后的生命周期,进而决定是否对组件进行 re-render(重渲染)。
在 React 中,只要父组件发生了更新,那么所有的子组件都会被无条件更新。这就导致了 ChildB 的 props 尽管没有发生任何变化,它本身也没有任何需要被更新的点,却还是会走一遍更新流程。
因此,我们可以再这个生命周期内进行判断:
shouldComponentUpdate(nextProps, nextState) {
// 判断 text 属性在父组件更新前后有没有发生变化,若没有发生变化,则返回 false
if(nextProps.text === this.props.text) {
return false
}
// 只有在 text 属性值确实发生变化时,才允许更新进行下去
return true
}
2、PureComponent相当于自动执行了一次shouldComponentUpdate的判断,PureComponent 与 Component 的区别点,就在于它内置了对 shouldComponentUpdate 的实现:PureComponent 将会在 shouldComponentUpdate 中对组件更新前后的 props 和 state 进行浅比较,并根据浅比较的结果,决定是否需要继续更新流程。“浅比较”将针对值类型数据对比其值是否相等,而针对数组、对象等引用类型的数据则对比其引用是否相等。
但是如果数据类型为引用类型,那么这种基于浅比较的判断逻辑就会带来这样两个风险:
- 若数据内容没变,但是引用变了,那么浅比较仍然会认为“数据发生了变化”,进而触发一次不必要的更新,导致过度渲染;
- 若数据内容变了,但是引用没变,那么浅比较则会认为“数据没有发生变化”,进而阻断一次更新,导致不渲染。
可以配合Immutable初始化不可变值来对引用对象类型进行初始化
3、函数组件的性能优化:React.memo 和 useMemo
React.memo 会帮我们“记住”函数组件的渲染结果,在组件前后两次 props 对比结果一致的情况下,它会直接复用最近一次渲染的结果。如果我们的组件在相同的 props 下会渲染相同的结果,那么使用 React.memo 来包装它将是个不错的选择。
React.memo(Component, areEqual);
这里的 areEqual 函数是一个可选参数,当我们不传入 areEqual 时,React.memo 也可以工作,此时它的作用就类似于 PureComponent——React.memo 会自动为你的组件执行 props 的浅比较逻辑。
和 shouldComponentUpdate 不同的是,React.memo 只负责对比 props,而不会去感知组件内部状态(state)的变化。
useMemo: 更加“精细”的 memo React.memo 控制是否需要重渲染一个组件,而 useMemo 控制的则是是否需要重复执行某一段逻辑。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);