Kodokon kodokon.com

Context: prop drilling なしで共有する

context を使って prop drilling なしでデータを行き渡らせ、その再レンダーと制約を管理下に置きます。

8 分 · 3 問

このレッスンを Kodokon で開く

Prop drilling とは、そこでは使われないのに 3 つ 4 つの中間コンポーネントを通り抜け、ツリーの葉に届くためだけに存在する props のことです。通過する層はどれも、興味のないデータと結合してしまいます。context はこの輸送の問題を解決します。createContext がチャネルを作り、プロバイダーが値を公開し、useContext が任意の子孫からそれを読み取ります。ニュアンスを忘れないでください。context は state を 輸送 するのであって、管理はしません。値は依然としてコンポーネント、ここではプロバイダーの中に存在します。

JSX
import { createContext, useMemo, useState } from "react";

const ThemeContext = createContext(null);

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("dark");
  const value = useMemo(
    () => ({ theme, setTheme }),
    [theme]
  );

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}
公開される値は参照を安定させるためにメモ化されています。

プロバイダーの値が変わると、すべてのコンシューマーが再レンダー されます。深くネストされたものも含めてです。しかも React.memo は守ってくれません。props は比較しても context は比較しないからです。典型的な罠がこれを悪化させます。value={{ theme, setTheme }} と書くとプロバイダーのレンダーごとに新しいオブジェクトが生成され、新しい参照が生まれ、theme が動いていなくても連鎖的な再レンダーが起きます。上の useMemo はそのためにあります。プロバイダーの外では大きな声で失敗するアクセスフックで仕上げましょう。

JSX
import { useContext } from "react";

function useTheme() {
  const context = useContext(ThemeContext);
  if (context === null) {
    throw new Error(
      "useTheme must be inside <ThemeProvider>"
    );
  }
  return context;
}
明示的なエラーは、静かな null に勝ります。

context を作る前に、まず コンポジション の選択肢を尽くしましょう。中間層が prop を中継するだけなら、構造を逆転させます。データが存在するツリーの上部で要素を組み立て、children や専用の prop を通じて下へ渡すのです。中間層は汎用になり、データはそれを使わないコンポーネントを一切通らなくなります。

JSX
function App({ currentUser }) {
  return (
    <Layout
      header={<TopBar user={currentUser} />}
      content={<Dashboard />}
    />
  );
}
Layout は currentUser について何も知りません。出来上がった要素をはめ込むだけです。

理解度チェック

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

  1. context のコンシューマーが React.memo で包まれていて、context の値が変わりました。何が起こりますか?
    • 再レンダーされない。React.memo がすべての更新をブロックする
    • 再レンダーされる。React.memo は context の値ではなく props を比較する
    • props も変わった場合にのみ再レンダーされる
  2. なぜプロバイダーで value に渡すオブジェクトをメモ化するのですか?
    • 各レンダーでのオブジェクト生成を高速化するため
    • プロバイダーが不変の値を要求するため
    • データが変わったときだけ参照が変わるようにし、コンシューマーの不要な再レンダーを避けるため
  3. 非常に頻繁に更新される state に対する context の主な制約は何ですか?
    • セレクターがない。更新のたびに、値の一部しか使わないものも含めてすべてのコンシューマーが再レンダーされる
    • context は非同期であり、更新を遅らせる
    • context はプリミティブ値しか格納できない