導入
「テーマ(ダーク/ライト)」や「ログイン中のユーザー」のように、アプリの広い範囲で使いたい値があります。これを props だけで配ろうとすると、途中の部品は使いもしないのに次へ渡すためだけに props を受け取ることになります。これが props のバケツリレーです。useContext を使うと、途中を経由せずに必要なコンポーネントへ直接値を届けられます。
説明
React.createContext(初期値) で「入れ物」を作り、上位で <Context.Provider value={...}> に包み、下位のどこからでも React.useContext(Context) で読み取ります。
flowchart TB provider["ThemeContext.Provider<br/>value: theme"] toolbar["Toolbar<br/>(themeを中継しない)"] button["ThemedButton<br/>useContext(ThemeContext)で直接取得"] provider --> toolbar toolbar --> button provider -.->|"Contextで直接届く(propsのバケツリレー不要)"| button
const ThemeContext = React.createContext("light");
function App() {
const [theme, setTheme] = React.useState("light");
return (
<ThemeContext.Provider value={theme}>
<div style={{ fontFamily: "system-ui" }}>
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
テーマ切り替え(現在: {theme})
</button>
<Toolbar />
</div>
</ThemeContext.Provider>
);
}
// Toolbar は theme をまったく使わないので、受け取りも中継もしない
function Toolbar() {
return (
<div style={{ padding: 12, border: "1px dashed #94a3b8", marginTop: 8 }}>
<ThemedButton />
</div>
);
}
// ThemedButton は2階層下にいるが、useContext で直接 theme を受け取れる
function ThemedButton() {
const theme = React.useContext(ThemeContext);
return (
<button
style={{
background: theme === "light" ? "#f1f5f9" : "#1e293b",
color: theme === "light" ? "#0f172a" : "#f8fafc",
border: "1px solid #94a3b8",
padding: "6px 12px",
}}
>
いまのテーマ: {theme}
</button>
);
}
React.createContext("light")… 入れ物(Context)を作ります。引数は「Provider に包まれなかったときのデフォルト値」です。<ThemeContext.Provider value={theme}>… このタグの内側すべてにthemeを配ります。React.useContext(ThemeContext)… 一番近い Provider のvalueを取り出します。Toolbarはthemeを props として受け取っていない点に注目してください。- Provider の
valueが変わると、useContextで読んでいるコンポーネントは自動で再描画されます。
やってみよう
Toolbar の中に、もう1つ別の場所で React.useContext(ThemeContext) を使う ThemedText コンポーネント(<p> でテーマ名を表示するだけ)を作って置いてみましょう。App や Toolbar を1文字も変えずに、新しい部品がテーマを受け取れることを確かめてください。