Reactのパフォーマンス最適化フックuseCallback
このレッスンでは、次の
パフォーマンス最適化のためのフック
useCallbackを検討します。
フックuseCallbackはAPI useMemoに似ていますが、
違いは、前者が再レンダリング間で値をキャッシュするのに対し、
後者はコールバックをキャッシュすることです。
これにより、必要でないときに負荷の高い
関数を再実行することを避けることができ、
関数の受け渡し
をする子コンポーネントで使用することができます。
例を使って詳しく見ていきましょう。
まず、コンポーネントAppを作成し、
その中にステートnumを用意します:
const [num, setNum] = useState(0);
クリックするとnumが1ずつ増えるボタンと、
numの値を表示する段落を配置しましょう:
return (
<div>
<button onClick={() => setNum(num + 1)}>click</button>
<p>clicks: {num}</p>
</div>
);
さて、ここで、Appに別のボタンを押すと追加される
要素のリストがさらに表示されると仮定しましょう。
このリストの要素を保持するために、
ステートitemsを用意します:
const [items, setItems] = useState([]);
そして、要素を追加する関数addItemを書きます:
function addItem() {
setItems([...items, 'new item']);
}
次に、リスト要素を表示するコードを書き、
それを子コンポーネントItemsに抽出しましょう。
このコンポーネントは、プロップスとして要素の配列と
追加する関数を受け取ります。
コンポーネントがいつ再レンダリングされるか
確認できるように、コンソールへの出力を忘れずに追加します:
function Items({ items, addItem }) {
const result = items.map((item, index) => {
return <p key={index}>{item}</p>;
});
console.log('Items render');
return (
<div>
<h3>Our items</h3>
{result}
<button onClick={addItem}>add item</button>
</div>
);
}
export default Items;
ItemsをAppコンポーネントの最後に配置し、
配列itemsと要素を追加する関数addItemを渡します:
return (
<>
<div>
<button onClick={() => setNum(num + 1)}>click</button>
<p>clicks: {num}</p>
<br />
</div>
<Items items={items} addItem={addItem} />
</>
);
では、ボタンをクリックしてみて、
numが増え、新しい要素がリストに追加されることを確認しましょう。
コンソールを開くと、リストが、
numを増やすボタンをクリックしたときでさえ、
毎回再レンダリングされていることがわかります。
リストが小さいうちは問題ありませんが、
もしリストが大きくて、さらに多くの要素があるとしたらどうでしょう?
大丈夫 – 前回のレッスンで、コンポーネントの不要な再レンダリングを
避けるためにAPI memoを検討したと言うかもしれません。
それでは、コンポーネントItemsをmemoでラップしてみましょう。
ちなみに、これはItemsをエクスポートする際に直接行うことができます:
export default memo(Items);
memoをインポートするのを忘れないでください:
import { memo } from 'react';
では、コンソールを開いてボタンをクリックしてみてください。
努力が無駄になりました!
コンポーネントをメモ化したのに、'click'ボタンを押すと、
コンポーネントItemsが毎回再レンダリングされてしまいます。
原因は、親コンポーネントが再レンダリングされると、
その関数が新しく再作成されることです。
これは、Itemsに渡している関数addItemにも当てはまります。
まさにこの時に、フックuseCallbackが役立ちます。
それでは、適用してみましょう。
まず、Appにインポートします:
import { useCallback } from 'react';
次に、単純な関数宣言addItemを
関数式に作り直し、
useCallbackの第一引数としてコールバック形式で関数を指定します。
第二引数の角括弧内に依存関係 – 関数内で使用されるすべてのリアクティブな変数、
この場合は配列items – を指定します:
const addItem = useCallback(() => {
setItems(() => [...items, 'New item']);
}, [items]);
完了です!
これで関数をキャッシュしました。
もう一度ボタンをクリックすると、
今度は'click'ボタンをクリックしても子コンポーネントが
再レンダリングされないことがわかります。
コンポーネントAppを作成し、
テキストを含む段落を配置してください。
初期値'text'のステートを作成し、
その値を段落内に表示してください。
段落をクリックすると、テキストの最後に感嘆符が追加されるようにしてください。
新しい製品を追加するボタンを持つ子コンポーネントProductsを作成してください。
それをApp内に配置してください。
親コンポーネントで、製品の配列を保持するステートと、
新しい製品を追加する関数を作成してください。
それらをプロップスとして子コンポーネントに渡し、
渡された配列をulリストとして表示してください。
Products内で、コンソールに'products render'というテキストを出力してください。
Productsをmemoでラップしてください。
段落とボタンをクリックしてみてください。
段落をクリックしたときに、子コンポーネントが依然として再レンダリングされることを確認してください。
製品を追加する関数を、useCallbackフックでラップしてキャッシュしてください。
段落とボタンをクリックしてみてください。
段落をクリックしたときに、子コンポーネントがもう再レンダリングされないことを確認してください。