この記事では、メモ化について理解し、メモ化が必要な理由と、パフォーマンスを向上させるために React にメモ化を実装する方法を紹介します。
このチュートリアルでは、React でメモ化を実装する方法を学びます。メモ化により、関数呼び出しの結果がキャッシュされ、再び必要になったときにそれらのキャッシュされた結果が返されるため、パフォーマンスが向上します。
次の内容について説明します:
この記事は、読者が React のクラスおよび関数コンポーネントの基本を理解していることを前提としています。
これらのトピックを確認したい場合は、React 公式ドキュメントコンポーネントとプロパティ
https://reactjs.org/docs を確認してください。 /components-and- props.html
React におけるメモ化の詳細について説明する前に、まず React が仮想 DOM を使用して UI をレンダリングする方法を見てみましょう。 [関連する推奨事項: Redis ビデオ チュートリアル ]
通常の DOM には、基本的にツリー形式で保存されたノードのセットが含まれています。 DOM 内の各ノードは UI 要素を表します。アプリケーションで状態変化が発生するたびに、その UI 要素とそのすべての子要素に対応するノードが DOM ツリー内で更新され、UI の再描画がトリガーされます。
効率的な DOM ツリー アルゴリズムのおかげでノードの更新は速くなりますが、DOM に多数の UI 要素がある場合は再描画が遅く、パフォーマンスに影響を与える可能性があります。そこで、Reactでは仮想DOMが導入されました。
これは、実際の DOM の仮想表現です。これで、アプリケーションの状態に変化があった場合、React は実際の DOM を直接更新せず、新しい仮想 DOM を作成します。 React は、この新しい仮想 DOM を以前に作成した仮想 DOM と比較し、違いを見つけて (翻訳者注: つまり、更新する必要があるノードを見つけます)、再描画します。
これらの違いに基づいて、仮想 DOM は実際の DOM をより効率的に更新できます。これにより、仮想 DOM は UI 要素とそのすべての子要素を単に更新するのではなく、実際の DOM で必要かつ最小限の変更のみを効果的に更新するため、パフォーマンスが向上します。
前のセクションでは、React が仮想 DOM を使用して DOM 更新操作を効率的に実行し、パフォーマンスを向上させる方法について説明しました。このセクションでは、パフォーマンスをさらに向上させるためにメモ化を使用する必要性を説明する例を示します。
count
という名前の変数をインクリメントするボタンを含む親クラスを作成します。親コンポーネントも子コンポーネントを呼び出し、パラメータを子コンポーネントに渡します。また、render
メソッドに console.log()
ステートメントを追加しました。
//Parent.js class Parent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } handleClick = () => { this.setState((prevState) => { return { count: prevState.count + 1 }; }); }; render() { console.log("Parent render"); return ( <div className="App"> <button onClick={this.handleClick}>Increment</button> <h2>{this.state.count}</h2> <Child name={"joe"} /> </div> ); } } export default Parent;
この例の完全なコードは、CodeSandbox## で参照できます。 # 。
親コンポーネントから渡されたパラメータを受け取り、それらを UI に表示するChild クラスを作成します。
//Child.js class Child extends React.Component { render() { console.log("Child render"); return ( <div> <h2>{this.props.name}</h2> </div> ); } } export default Child;
count の値が変更されます。状態が変化したので、親コンポーネントの
render メソッドが実行されます。
count をインクリメントし続けると、次の出力が得られます。
Parent render Child render Parent render Child render Parent render Child render
sandbox で上記の例を体験し、コンソール出力。
出力から、親コンポーネントが再レンダリングされると、子コンポーネントに渡されたパラメータが変更されていない場合でも、子コンポーネントも再レンダリングされることがわかります。これにより、子コンポーネントの仮想 DOM が以前の仮想 DOM との差分チェックを実行します。子コンポーネントには変更がなく、すべてのプロップは再レンダリング時に変更されないため、実際の DOM は更新されません。 実際の DOM が不必要に更新されないのはパフォーマンスにとって非常に良いことですが、子コンポーネントに実際の変更がなくても、新しい仮想 DOM が作成され、差分チェックが実行されることがわかります。 。小規模な React コンポーネントの場合、このパフォーマンス コストは無視できますが、大規模なコンポーネントの場合、パフォーマンスへの影響が大きくなる可能性があります。この仮想 DOM の再レンダリングと差分チェックを回避するために、メモ化を使用します。在 React 应用的上下文中,Memoization 是一种手段,每当父组件重新渲染时,子组件仅在它所依赖的 props 发生变化时才会重新渲染。如果子组件所依赖的 props 中没有更改,则它不会执行 render 方法,并将返回缓存的结果。由于渲染方法未执行,因此不会有虚拟 DOM 创建和差异检查,从而实现性能的提升。
现在,让我们看看如何在类和函数组件中实现 Memoization,以避免这种不必要的重新渲染。
为了在类组件中实现 Memoization,我们将使用 React.PureComponent。React.PureComponent
实现了 shouldComponentUpdate(),它对 state
和 props
进行了浅比较,并且仅在 props 或 state 发生更改时才重新渲染 React 组件。
将子组件更改为如下所示的代码:
//Child.js class 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 render Child render Parent render Parent render
对于首次渲染,它同时调用父组件和子组件的 render
方法。
对于每次增加 count
后的重新渲染,仅调用父组件的 render
函数。子组件不会重新渲染。
为了在函数组件中实现 Memoization,我们将使用 React.memo()。React.memo()
是一个高阶组件(HOC),它执行与 PureComponent
类似的工作,来避免不必要的重新渲染。
以下是函数组件的代码:
//Child.js export function Child(props) { console.log("Child render"); return ( <div> <h2>{props.name}</h2> </div> ); } export default React.memo(Child); // 这里我们给子组件添加 HOC 实现 Memoization
同时还将父组件转换为了函数组件,如下所示:
//Parent.js export 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 render Child render Parent render Parent render Parent render
在上面的示例中,我们看到,当我们对子组件使用 React.memo()
HOC 时,子组件没有重新渲染,即使父组件重新渲染了。
但是,需要注意的一个小问题是,如果我们将函数作为参数传递给子组件,即使在使用 React.memo()
之后,子组件也会重新渲染。让我们看一个这样的例子。
我们将更改父组件,如下所示。在这里,我们添加了一个处理函数,并作为参数传递给子组件:
//Parent.js export 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.js export 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()
来避免更多的重复渲染导致子组件重新渲染的主要问题是重新创建了 handler
函数,这更改了传递给子组件的引用。因此,我们需要有一种方法来避免这种重复创建。如果未重新创建 handler
函数,则对 handler
函数的引用不会更改,因此子组件不会重新渲染。
为了避免每次渲染父组件时都重新创建函数,我们将使用一个名为 useCallback() 的 React Hook。Hooks 是在 React 16 中引入的。要了解有关 Hooks 的更多信息,你可以查看 React 的官方 hooks 文档,或者查看 `React Hooks: How to Get Started & Build Your Own"。
useCallback()
钩子传入两个参数:回调函数和依赖项列表。
以下是 useCallback()
示例:
const handleClick = useCallback(() => { //Do something }, [x,y]);
在这里,useCallback()
被添加到 handleClick()
函数中。第二个参数 [x, y]
可以是空数组、单个依赖项或依赖项列表。每当第二个参数中提到的任何依赖项发生更改时,才会重新创建 handleClick()
函数。
如果 useCallback()
中提到的依赖项没有更改,则返回作为第一个参数提及的回调函数的 Memoization 版本。我们将更改父组件,以便对传递给子组件的处理程序使用 useCallback()
钩子:
//Parent.js export default function Parent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; const handler = useCallback(() => { // 给 handler 函数使用 useCallback() console.log("handler"); }, []); console.log("Parent render"); return ( <div className="App"> <button onClick={handleClick}>Increment</button> <h2>{count}</h2> <Child name={"joe"} childFunc={handler} /> </div> ); }
子组件代码将保持原样。
此示例的完整代码这个 sandbox 中。
当我们在上述代码的父组件中增加 count
时,我们可以看到以下输出:
Parent render Child render Parent render Parent render Parent render
由于我们对父组件中的 handler
使用了 useCallback()
钩子,因此每次父组件重新渲染时,都不会重新创建 handler
函数,并且会将 handler
的 Memoization 版本传递到子组件。子组件将进行浅比较,并注意到 handler
函数的引用没有更改,因此它不会调用 render
方法。
Memoization 是一种很好的手段,可以避免在组件的 state 或 props 没有改变时对组件进行不必要的重新渲染,从而提高 React 应用的性能。你可能会考虑为所有组件添加 Memoization,但这并不一定是构建高性能 React 组件的方法。只有在组件出现以下情况时,才应使用 Memoization:
在本教程中,我们理解了:
React.memo()
和类组件的 React.PureComponent
实现 MemoizationReact.memo()
之后,子组件也会重新渲染useCallback()
钩子来避免在函数作为 props 传递给子组件时产生重新渲染的问题希望这篇 React Memoization 的介绍对你有帮助!
原文地址:https://www.sitepoint.com/implement-memoization-in-react-to-improve-performance/
原文作者:Nida Khan
更多编程相关知识,请访问:编程视频!!
以上がメモ化を使用して React のパフォーマンスを向上させる方法について話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。