概述
使用 React.memo 并不加思考地将 lodash 的 isEqual 作为比较函数,是一种反模式。
更抽象地说,在 React.memo 中使用高成本的比较函数,是一种反模式。
为什么
总是重渲染的组件能正确地工作,一般情况下也不是性能瓶颈。
React.memo 作为一种优化手段,旨在允许开发者通过 低成本的参数比较 来避免 高成本的重渲染。因此,比较函数如果本身执行成本很高,就与这里的优化理念背道而驰,得不偿失。
潜在危害
使用 React.memo 对组件进行优化时,如果使用不当,反而会产生危害,造成性能劣化。
以标题所列的模式为例。lodash 的 isEqual 是深比较函数,当比较对象引用相等时,则会提前返回,否则,则会递归地进行深比较。
const MemoComp = React.memo(Comp, _.isEqual)
单次比较成本高
当 MemoComp 在一次重渲染时,如果 prevProps.item 和 nextProps.item 是等值但不同的两个对象,那么它们的比较结果是 true,虽然不会触发底层组件重渲染,但这次比较的成本却会非常高。
持续的性能劣化
这是一个比较隐蔽的危害。
比如 item1 和 item2 是等值但不同的两个对象。一开始用 item1 渲染了一个 MemoComp 组件,但后续不断地使用 item2 来渲染该组件,则每次在 memo 层比较的时候,prevProps.item 都是 item1,而 nextProps.item 都是 item2,每次都会重演上述『单次比较成本高』的惨案。
这是因为,由于比较函数在每次比较后都返回了 true,导致底层组件其实一直没有重渲染,它的 props 一直没有变,而 props.item 也一直都是 item1,没法收敛为 item2,所以每次 memo 层比较时取底层组件的 props 来作为 prevProps 时,其实都是取到的最初的版本。
这种现象只有等到 MemoComp 销毁重建或某次比较返回了 false 迫使底层组件发生重渲染并更新 props 才会结束。但上一轮持续劣化结束了,接下来到底是进入正常状态,还是又进入下一轮持续劣化状态,又谁知道呢。
解决方案
- 不使用
React.memo - 使用
React.memo时,需要确保比较函数的执行成本远低于重渲染成本才有收益
老生常谈
- 不要过早地进行性能优化
- 不要进行未加思考的性能优化
- 不要进行包治百病的性能优化(如果存在,它已经被做进了底层)
- 找瓶颈 => 找原因 => 定方案 => 做优化