在react函数组件中修改状态会触发整个函数组件的重载,重载过程中会导致函数中的方法重载和组件重新渲染,这个过程中有很多重载和重新渲染是不必要的,我们可以使用memo和useCallback方法来阻止不必要的性能消耗。
首先定义三个基础组件,来观察不使用上述方法之前的渲染情况:
在最外层父组件中声明count状态和更改count的方法setCount,并定义一个将count重置为0的方法传递给Foo组件,当我们触发count更改时整个App函数都会重载,重载过程中会导致reset方法也被重载,App组件当中引入的Foo和Pure组件都会重新渲染。
如上图所示,每次点击按钮都会触发两个子组件的重新渲染,但是事实上这两个子组件的重新渲染是没有必要的。
memo:
memo的作用是在组件重新渲染前确认内部传入的组件是否需要重新渲染。
这里将Pure组件用memo方法进行嵌套。
然后再次点击按钮更改状态触发App组件的重新渲染
可以看到这里再次进行点击,重新渲染的子组件就只有Foo了,由于Pure内部没有状态和属性更改,memo会判定该组件无需重新渲染
useCallback:
useCallback的作用是缓存一个函数,并传入相关的依赖项,只有在依赖项改变的时候才会重载函数
我们首先给Foo也包裹memo方法
点击按钮更改状态触发App组件的重新渲染
发现即使嵌套了memo,Foo组件还是会不断重新渲染,原因是我们从父组件传入的reset在父组件重载的过程中也被重载了,新的reset !== 上次传入的reset,由于属性发生了更改,因此被认为有必要进行重新渲染,这时就应该用useCallback将reset方法进行缓存,阻止这种不必要的重新渲染。
此时再触发状态更改
两个组件都只有初始化渲染,不再触发重新渲染了。
附代码:
import React, { memo, useCallback, useState } from "react";
// 最外层父组件
function App() {
const [count, setCount] = useState(0)
const reset = useCallback(() => {
setCount(0)
}, [])
return (
<div className="App">
<div>{count}</div>
<button onClick={() => setCount(count + 1)}>add</button>
<Foo reset={reset} />
<Pure />
</div>
);
}
// 接收函数
const Foo = memo(function Foo(props) {
console.log('Foo render...');
return (
<button onClick={props.reset}>
reset
</button>
)
})
// 纯函数组件
const Pure = memo(function Pure() {
console.log('pureComponent render...');
return (
<div>pureComponent</div>
)
})
export default App;