聊聊怎么利用Memoization提高React性能( 二 )


React 中的 Memoization在 React 应用的上下文中,Memoization 是一种手段,每当父组件重新渲染时,子组件仅在它所依赖的 props 发生变化时才会重新渲染 。 如果子组件所依赖的 props 中没有更改,则它不会执行 render 方法,并将返回缓存的结果 。 由于渲染方法未执行,因此不会有虚拟 DOM 创建和差异检查,从而实现性能的提升 。
现在,让我们看看如何在类和函数组件中实现 Memoization,以避免这种不必要的重新渲染 。
类组件实现 Memoization为了在类组件中实现 Memoization,我们将使用 React.PureComponent 。 React.PureComponent 实现了 shouldComponentUpdate(),它对 stateprops 进行了浅比较,并且仅在 props 或 state 发生更改时才重新渲染 React 组件 。
将子组件更改为如下所示的代码:
//Child.jsclass Child extends React.PureComponent { // 这里我们把 React.Component 改成了 React.PureComponent render() { console.log("Child render"); return ( <div> <h2>{this.props.name}</h2> </div> ); }}export default Child;此示例的完整代码显示在这个 sandbox 中 。
父组件保持不变 。 现在,当我们在父组件中增加 count 时,控制台中的输出如下所示:
Parent renderChild renderParent renderParent render对于首次渲染,它同时调用父组件和子组件的 render 方法 。
对于每次增加 count 后的重新渲染,仅调用父组件的 render 函数 。 子组件不会重新渲染 。
函数组件实现 Memoization为了在函数组件中实现 Memoization,我们将使用 React.memo() 。 React.memo() 是一个高阶组件(HOC),它执行与 PureComponent 类似的工作,来避免不必要的重新渲染 。
以下是函数组件的代码:
//Child.jsexport function Child(props) { console.log("Child render"); return ( <div> <h2>{props.name}</h2> </div> );}export default React.memo(Child); // 这里我们给子组件添加 HOC 实现 Memoization同时还将父组件转换为了函数组件,如下所示:
【聊聊怎么利用Memoization提高React性能】//Parent.jsexport default function Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; console.log("Parent render"); return ( <div> <button onClick={handleClick}>Increment</button> <h2>{count}</h2> <Child name={"joe"} /> </div> );}此示例的完整代码可以在这个 sandbox 中看到 。
现在,当我们递增父组件中的 count 时,以下内容将输出到控制台:
Parent renderChild renderParent renderParent renderParent renderReact.memo() 存在的问题在上面的示例中,我们看到,当我们对子组件使用 React.memo() HOC 时,子组件没有重新渲染,即使父组件重新渲染了 。
但是,需要注意的一个小问题是,如果我们将函数作为参数传递给子组件,即使在使用 React.memo() 之后,子组件也会重新渲染 。 让我们看一个这样的例子 。
我们将更改父组件,如下所示 。 在这里,我们添加了一个处理函数,并作为参数传递给子组件:
//Parent.jsexport default function Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; const handler = () => { console.log("handler"); // 这里的 handler 函数将会被传递给子组件 }; console.log("Parent render"); return ( <div className="App"> <button onClick={handleClick}>Increment</button> <h2>{count}</h2> <Child name={"joe"} childFunc={handler} /> </div> );}子组件代码将保持原样 。 我们不会在子组件中使用父组件传递来的函数:
//Child.jsexport function Child(props) { console.log("Child render"); return ( <div> <h2>{props.name}</h2> </div> );}export default React.memo(Child);现在,当我们递增父组件中的 count 时,它会重新渲染并同时重新渲染子组件,即使传递的参数中没有更改 。
那么,是什么原因导致子组件重新渲染的呢?答案是,每次父组件重新渲染时,都会创建一个新的 handler 函数并将其传递给子组件 。 现在,由于每次重新渲染时都会重新创建 handle 函数,因此子组件在对 props 进行浅比较时会发现 handler 引用已更改,并重新渲染子组件 。
接下来,我们将介绍如何解决此问题 。
通过 useCallback() 来避免更多的重复渲染导致子组件重新渲染的主要问题是重新创建了

推荐阅读