Kodokon kodokon.com

メモ化: memo、useMemo、useCallback

React.memo、useMemo、useCallback がいつ本当にアプリを速くし、いつ意味もなく複雑にするのかを学びます。

10 分 · 3 問

このレッスンを Kodokon で開く

コンポーネントが再レンダーされると、その子はすべてデフォルトで再レンダーされます。props が変わったかどうかに関係なくです。それは悲劇ではありません。レンダーは出力が変わったときにのみ DOM に触れます。メモ化が意味を持つのは、頻繁なレンダーと高コストなサブツリーが出会うときです。3 つの道具、3 つの標的があります。React.memo は props が浅く等しければコンポーネントのレンダーをスキップし、useMemo は計算された をキャッシュし、useCallback関数 をキャッシュします。3 つとも参照の等価性に依存しています。

JSX
import { memo } from "react";

const Row = memo(function Row({ item, onSelect }) {
  return (
    <li onClick={() => onSelect(item.id)}>{item.label}</li>
  );
});
Row は item または onSelect の参照が変わったときにのみ再レンダーされます。

鎖の強さはもっとも弱い環と同じです。1 つの prop でもレンダーごとに新しい参照を受け取れば - インラインのアロー関数、オブジェクトや配列のリテラル - memo の比較は失敗し、コンポーネントはいずれにせよ再レンダーされます。useCallback は関数の参照を安定させ、比較が成功するようにします。

JSX
import { useCallback } from "react";

function List({ items }) {
  const handleSelect = useCallback((id) => {
    console.log("selected:", id);
  }, []);

  return (
    <ul>
      {items.map((item) => (
        <Row
          key={item.id}
          item={item}
          onSelect={handleSelect}
        />
      ))}
    </ul>
  );
}
handleSelect はレンダーごとに同じ参照を保ちます。

useMemo には正当な用途が 2 つあります。本当に高コストな計算 を避けること(数千件の項目のソートや集計)、そしてメモ化された子が消費する、あるいはエフェクトの依存配列に並ぶ 参照を安定させる ことです。ささいな計算では、キャッシュのほうが節約より高くつきます。依存関係の比較、保持されるメモリ、重くなる読解です。

JSX
import { useMemo } from "react";

function Stats({ orders }) {
  const total = useMemo(() => {
    return orders.reduce(
      (sum, order) => sum + order.amount,
      0
    );
  }, [orders]);

  return <p>Total: {total} €</p>;
}
合計は orders の参照が変わったときにのみ再計算されます。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. 子が React.memo で包まれているのに、親がインラインのアロー関数を prop として渡します。結果は?
    • メモ化は機能する。関数はソースコードで比較される
    • メモ化は効かない。インライン関数はレンダーごとに新しい参照を作り、比較が失敗する
    • React が開発時に警告を出す
  2. useCallback(fn, [a]) は a が変わらない限り何を保証しますか?
    • 関数がメモリ上で決して再生成されないこと
    • 関数が一度だけ実行されること
    • レンダー間で同じ関数の参照が返されること
  3. どの場合に useMemo は明確に正当化されますか?
    • 高コストな計算、あるいはメモ化された子やエフェクトの依存のために安定させるべき参照
    • 念のため、あらゆる派生値
    • コンポーネントの 2 回のマウントをまたいでデータをキャッシュすること