VUE3.0 をすぐに使い始める方法: はじめ
に このチュートリアルでは、React でメモ化を実装する方法を学びます。メモ化により、関数呼び出しの結果がキャッシュされ、再度必要になったときにそれらのキャッシュされた結果が返されるため、パフォーマンスが向上します。
以下について説明します:
この記事は、React のクラスおよび関数コンポーネントの基本を理解していることを前提としています。
これらのトピックを確認したい場合は、公式 React ドキュメントのコンポーネントとプロパティ
https://reactjs.org/docs/components-and-props.html を
確認してください。
React のメモ化の詳細について説明する前に、まず React が仮想 DOM を使用して UI をレンダリングする方法を見てみましょう。
通常の 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 { コンストラクター(小道具) { スーパー(小道具); this.state = { カウント: 0 }; } ハンドルクリック = () => { this.setState((prevState) => { return { カウント: prevState.count + 1 }; }); }; 与える() { console.log("親レンダー"); 戻る ( <div className="アプリ"> <button onClick={this.handleClick}>増分</button> <h2>{this.state.count}</h2> <子供の名前={"ジョー"} /> </div> ); } }この
例
の完全なコードは CodeSandbox で参照できます。
親コンポーネントから渡されたパラメーターを受け取り、それらを UI に表示する子Child
を作成します。
//Child.js class Child extends React.Component { 与える() { console.log("子レンダリング"); 戻る ( <div> <h2>{this.props.name}</h2> </div> ); } }
親コンポーネントのボタンをクリックするたびに、 count
値が変更されます
。
状態が変化したので、親コンポーネントのrender
メソッドが実行されます。
子コンポーネントに渡されるパラメータは、親コンポーネントが再レンダリングされるたびに変更されるわけではないため、子コンポーネントは再レンダリングすべきではありません。ただし、上記のコードを実行してcount
をインクリメントし続けると、次の出力が得られます
。 子レンダリング 親レンダリング 子レンダリング 親レンダリング 子レンダリング
このサンドボックスで上記の例を試して、コンソール出力を表示できます。
出力から、親コンポーネントが再レンダリングされると、子コンポーネントに渡されたパラメータが変更されていない場合でも、子コンポーネントも再レンダリングされることがわかります。これにより、子コンポーネントの仮想 DOM が以前の仮想 DOM との差分チェックを実行します。子コンポーネントには変更がなく、すべてのプロップは再レンダリング時に変更されないため、実際の DOM は更新されません。
実際の DOM が不必要に更新されないことは間違いなくパフォーマンス上の利点ですが、子コンポーネントに実際の変更がない場合でも、新しい仮想 DOM が作成され、差分チェックが実行されることがわかります。小規模な React コンポーネントの場合、このパフォーマンス コストは無視できますが、大規模なコンポーネントの場合、パフォーマンスへの影響が大きくなる可能性があります。この仮想 DOM の再レンダリングと差分チェックを回避するために、メモ化を使用します。
React アプリケーションのコンテキストでは、メモ化は、親コンポーネントが再レンダリングされるたびに、子コンポーネントは依存する props が変更された場合にのみ再レンダリングする手段です。子コンポーネントが依存する props に変更がない場合、render メソッドは実行されず、キャッシュされた結果が返されます。 render メソッドが実行されないため、仮想 DOM の作成や差分チェックが行われず、パフォーマンスが向上します。
ここで、この不必要な再レンダリングを回避するために、クラスおよび関数コンポーネントにメモ化を実装する方法を見てみましょう。
クラス コンポーネントでメモ化を実装するには、React.PureComponent を使用します。 React.PureComponent
shouldComponentUpdate() を実装します。これはstate
とprops
の浅い比較を行い、props または state が変更された場合にのみ React コンポーネントを再レンダリングします。
子コンポーネントを次のようなコードに変更します:
//Child.js class Child extends React.PureComponent { // ここで React.Component を React.PureComponent に変更します 与える() { console.log("子レンダリング"); 戻る ( <div> <h2>{this.props.name}</h2> </div> ); } }
この例の完全なコードは、このサンドボックスに示されています
。
親コンポーネントは変更されません。
ここで、親
コンポーネントのcount
インクリメントすると、コンソールの出力は次のようになります。
子レンダリング 親レンダリング 親のレンダリング
最初のレンダリングでは、親コンポーネントと子コンポーネントの両方のrender
メソッドを呼び出します。
count
増やした後の再レンダリングのたびに、親コンポーネントのrender
関数のみが呼び出されます。子コンポーネントは再レンダリングされません。
関数コンポーネントでメモ化を実装するには、React.memo() を使用します。 React.memo()
不要な再レンダリングを避けるためにPureComponent
と同様の作業を実行する高次コンポーネント (HOC) です。
関数コンポーネントのコードは次のとおりです:
//Child.js エクスポート関数 Child(props) { console.log("子レンダリング"); 戻る ( <div> <h2>{props.name}</h2> </div> ); }import default React.memo(Child);
//
ここでは、以下に示すように、子コンポーネントに HOC を追加してメモ化を実装し
、親コンポーネントを関数コンポーネントに変換します。
デフォルト関数 Parent() をエクスポート { const [カウント, setCount] = useState(0); const handleClick = () => { setCount(カウント + 1); }; console.log("親レンダー"); 戻る ( <div> <button onClick={handleClick}>増分</button> <h2>{count}</h2> <子供の名前={"ジョー"} /> </div> );
この例の完全なコードは、このサンドボックスで確認できます
。
ここで、親コンポーネントのcount
インクリメントすると、以下がコンソールに出力されます:
Parent render 子レンダリング 親レンダリング 親レンダリング 親の renderReact.memo()
上記の例では、子コンポーネントでReact.memo()
HOC を使用すると、親コンポーネントが再レンダリングされても、子コンポーネントは再レンダリングされないことがわかります。
ただし、注意すべき点が 1 つあります。それは、関数をパラメータとして子コンポーネントに渡すと、 React.memo()
使用した後でも子コンポーネントが再レンダリングされるということです。この例を見てみましょう。
親コンポーネントを以下のように変更します。ここではハンドラー関数を追加し、それをパラメーターとして子コンポーネントに渡します。
//Parent.js デフォルト関数 Parent() をエクスポート { const [カウント, setCount] = useState(0); const handleClick = () => { setCount(カウント + 1); }; const ハンドラー = () => { console.log("handler"); // ここでのハンドラー関数は子コンポーネントに渡されます。 console.log("親レンダー"); 戻る ( <div className="アプリ"> <button onClick={handleClick}>増分</button> <h2>{count}</h2> <Child name={"joe"} childFunc={handler} /> </div> );
子コンポーネントのコードはそのまま残ります
。
親コンポーネントから渡された関数を子コンポーネントで使用しません:
//Child.js エクスポート関数 Child(props) { console.log("子レンダリング"); 戻る ( <div> <h2>{props.name}</h2> </div> ); }ここで、
親
コンポーネントのcount
インクリメントすると、渡されたパラメーターに変更がないにもかかわらず、再レンダリングと子コンポーネントの再レンダリングが同時に行われます。
では、サブコンポーネントが再レンダリングされる原因は何でしょうか?答えは、親コンポーネントが再レンダリングされるたびに、新しいhandler
関数が作成され、子コンポーネントに渡されるということです。現在、再レンダリングのたびにhandle
関数が再作成されるため、子コンポーネントは props の浅い比較を行うときにhandler
参照が変更されたことを検出し、子コンポーネントを再レンダリングします。
次に、この問題を解決する方法について説明します。
useCallback()
使用して再レンダリングを回避する子コンポーネントの再レンダリングに関する主な問題は、 handler
関数が再作成され、子コンポーネントに渡される参照が変更されることです。したがって、この重複を回避する方法が必要です。 handler
関数が再作成されない場合、 handler
関数への参照は変更されないため、子コンポーネントは再レンダリングされません。
親コンポーネントがレンダリングされるたびに関数を再作成する必要がないように、useCallback() と呼ばれる React フックを使用します。フックは React 16 で導入されました。フックの詳細については、React の公式フック ドキュメントをチェックするか、「React Hooks: How to Get Started & Build Your Own」をチェックしてください。
useCallback()
フックは、コールバック関数と依存関係のリストという 2 つのパラメータを受け取ります。以下
はuseCallback()
の例です:
const handleClick = useCallback(() => { //何かをする ここ
では、 handleClick()
関数にuseCallback()
が追加されています。 2 番目のパラメーター[x, y]
には、空の配列、単一の依存関係、または依存関係のリストを指定できます。 handleClick()
関数は、2 番目のパラメーターに記載されている依存関係が変更された場合にのみ再作成されます。
useCallback()
で言及されている依存関係が変更されていない場合、最初の引数として言及されたコールバック関数のメモ化されたバージョンが返されます。子コンポーネントに渡されるハンドラーでuseCallback()
フックを使用するように親コンポーネントを変更します。
//Parent.js デフォルト関数 Parent() をエクスポート { const [カウント, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; const handler = useCallback(() => { // ハンドラー関数には useCallback() を使用します console.log( "Handler"); }、[]); console.log("親レンダー"); 戻る ( <div className="アプリ"> <button onClick={handleClick}>増分</button> <h2> {count} </h2> <Child name={"joe"} childFunc={handler} /> </div> );
子コンポーネントのコードはそのまま残ります
。
この例の完全なコードはこのサンドボックスにあります。
上記のコードの親
コンポーネントでcount
インクリメントすると、次の出力が表示されます。
子レンダリング 親レンダリング 親レンダリング 親
コンポーネントのhandler
にuseCallback()
フックを使用するため、親コンポーネントが再レンダリングされるたびにhandler
関数は再現されず、 handler
のメモバージョンが子コンポーネントに渡されます。子コンポーネントは浅い比較を行い、 handler
関数への参照が変更されていないため、 render
方法を呼び出さないことに注意してください。
Memoization 是一种很好的手段,可以避免在组件的state 或props 没有改变时对组件进行不必要的重新渲染,从而提高React 应用的性能。すべてのコンポーネントにメモ化を追加することを検討するかもしれませんが、それが必ずしも高パフォーマンスの React コンポーネントを構築する方法であるとは限りません。 Memoization should only be used if the component:
In this tutorial we understood:
React.memo()
、クラスコンポーネントにはReact.PureComponent
を通じて React にメモ化を実装します。React.memo()
を使用した後でも子コンポーネントがリセットされることを示しますuseCallback()
avoid re-rendering problems when functions are passed as props to child components.I hope this introduction to React Memoization will be helpful to you!
元のアドレス: https://www.sitepoint.com/implement-memoization-in-react-to-improve-performance/原
著者: Nida Khan