Description
当我们谈 React 性能优化的时候,通常是在谈对 组件性能
进行优化。像一些普适的性能优化思路,比如资源加载优化,代码压缩,减少重绘与回流,启用 CDN 等等都是通用的。而我们写 React,其实就是在跟组件打交道,其它框架层面的优化,React 都帮我们搞定了。作为开发者,我们需要对自己的 React 组件代码适当运用一些优化手段。那么对组件来说,核心思路只有一个:避免重复渲染
。与此有关的 API 或生命周期方法有:useCallback
,useMemo
,React.memo
,React.PureComponent
,shouldComponentUpdate
。这其中,有些是应用在类组件的,有些是在函数式组件里使用的。
类组件的性能优化
对于类组件来说,主要是 shouldComponentUpdate
,这个生命周期方法返回一个布尔值,React 会根据这个值决定是否执行后续的生命周期方法,进而决定组件是否重新渲染。它默认返回 true,表示无条件渲染;我们可以在这个方法里书写逻辑,当 props 或 state 无变化时返回 false,来阻止掉冗余的渲染。举个例子:
class A extends React.Component {
state = {
text1: '1',
text2: '2'
}
render() {
return (
<div>
<B text={this.state.text1} />
<C text={this.state.text2} />
</div>
)
}
}
现在我们有父组件 A,子组件 B 和 C。其中子组件的 text 数据是由父组件通过 props 传入的。我们知道,在 React 里,一旦父组件更新了,所有关联的子组件也会跟着更新。这带来的问题是:上面的例子我如果只想更新 text1 ,影响其实是 B 组件,但是因为所有组件都刷新了,C 组件的 text 没有发生变化,但它也跟着渲染更新了。所以这时我们就可以在 C 组件里添加 shouldComponentUpdate
的逻辑:
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.text === this.props.text) {
return false
}
return true
}
通过比较更新前后 props 的 text 属性是否相等,来决定是否重新渲染 C 组件。其实 React 有个类可以专门来做这件事,这就是React.PureComponent
,这个类相当于在 shouldComponentUpdate 里帮我们把优化工作做好了,但是它只会浅比较组件的 props 和 state ,所谓浅比较
就是对基本数据类型比较值是否相等,而对引用类型则比较引用是否相等。这会带来问题:数据内容不变,但是引用变了;或者数据引用变了,但是内容没变,这两种情况都会导致浅比较出现异常,导致渲染问题。所以我们一般会借助 Immutable.js
这个库来帮助创建不可变数据
,这个库提供的 API 可以创建前后引用不相等的数据,这样就可以确保 PureComponent 的比较逻辑正常工作。
import { List } from 'immutable'
const emptyList = List()
const addList = emptyList.push(1) // [1]
console.log(emptyList === addList) // false
class HelloWorld extends React.PureComponent {
state = {}
constructor() {}
render() {}
}
函数式组件性能优化
类组件的 shouldComponentUpdate/PureComponent
对应到函数式组件,就是 React.memo
API,这个函数是 React 提供一个高阶组件,它接收两个参数:
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
第一个函数是我们写的组件,第二个参数是比较函数,React 会根据第二个参数的比较结果,决定是否复用函数的渲染缓存结果,如果不传第二个参数,默认会进行 props 的浅比较,效果相当于 PureComponent
。值得一提的是,React.memo 只能对 props 进行比较,无法感知组件内部 state 的变化。
React.memo
是对整个组件进行缓存优化,那如果我们只想对函数中的某一段逻辑进行缓存优化呢?这时就要靠 useMemo
和 useCallback
来帮忙了,这两个都是 React Hooks。
useMemo
的用法如下:
const cachedValue = useMemo(calculateValue, dependencies)
它接收两个参数,第一个参数是个计算值,比如你传入函数,函数的返回值会被缓存;第二个参数是依赖项,只有依赖发生变化时才会重新执行逻辑计算新的值。
useCallback
的用法如下:
const cachedFn = useCallback(fn, dependencies)
它接收两个参数:第一个参数是个函数,第二个参数是依赖项。那 useMemo
和 useCallback
有啥区别呢?
- useMemo 缓存的是
值
,也就是函数返回的任意值都会被缓存 - useCallback 缓存的是
整个函数
,也就是你通过 useCallback 包装,让这个函数有了缓存结果 - 也就是说,
useCallback
是函数版的useMemo
,它作用的是函数。
总结
- React 性能优化的思路是
避免组件重复渲染
- 对于类组件,可以通过
shouldComponentUpdate
生命周期以及React.PureComponent
基类优化 - 对于函数式组件,可以通过
React.memo
和useMemo
,useCallback
优化