在 react 中如果数据没发生变化,则真实的 dom 不会发生改变。但是 dom 不发生改变并不代表 react 中不会产生其他耗时的计算。如果一个组件会产生大量的子组件,那单单是这些子组件 diff 就会产生大量的耗时操作,在某些场景下可能会引起页面卡顿。
一个例子
这里有一个典型的例子,父组件 App 中有一个输入框和子组件 Child。Child 接受来自父组件的状态 text。从代码中可以看出,这个 text 一直保持不变,丝毫不受 input 的影响。那么如果这时候输入的值发生了改变,子组件是否会 render 呢(这里指子组件是否会被调用并且 diff,并不是指真实的 dom render)?
function Child(props){
console.info("child call render")
return (
<p>{this.props.text}</p>
)
}
function App() {
const [value, setValue] = useState()
const [text, setText] = useState({text: 'hello'})
return (
<>
<input onChange = {e => setValue(e.target.value)} />
<Child text={hello}/>
</>
)
}
答案是: 会!。也就是虽然父组件传递给子组件的值没有发生任何改变,但实际上子组件仍然会 render。其原因也非常简单:
- react 如果判断本次 props 和上一次 props 不一致,则一定会 render 该组件,但是这个不一致指的是
===
而不是 props 的内容。 -
<Child text={hello}/>
实际上是转换成了React.createElement(Child, {text: 'hello'}))
。而这里的{text: 'hello'}
则是传递给 Child 的 props。每当 App 状态发生改变时,都会创建一个新的匿名对象{text: 'hello'}
。虽然内容不同,但是引用却改变了。因此导致了 Child render。
React.memo
这样的情况,类组件可以通过 componentShouldUpdate
来实现用户自定义的比较。而对于函数组件在没有对应的生命周期的情况下,则可以采用 React.memo
来解决这个问题。React.memo 接受两个参数:
- 需要 memo 的组件
- compare 方法(默认为
shallowEqual
)
即利用该方法可以实现与 componentShouldUpdate
相似的功能,每次比较时不采用 === 而是采用 compare 方法,当该方法返回 true 则表示 props 没有变化。shallowEqual 也非常简单:
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
而 React.memo 的实现更是非常简洁:
function memo(type, compare) {
{
if (!isValidElementType(type)) {
error('memo: The first argument must be a component. Instead ' + 'received: %s', type === null ? 'null' : typeof type);
}
}
return {
$$typeof: REACT_MEMO_TYPE,
type: type,
compare: compare === undefined ? null : compare
};
}
几乎没有做任何事情,只是将该组件声明为 REACT_MEMO_TYPE
告诉 React 比较时不要采用 === 而是采用用户自定义的比较函数或者默认采用 shallowEqual
。
网友评论