- React
React useContextの使い方|再レンダリング対策まで
useContextとは、Provider で配った値を、途中のコンポーネントを経由せずに深い階層から直接読み取るための React のフックです。 使い方は「createContext で作る → Provider で配る → useContext で読む」の3ステップだけで、props を何階層も受け渡す「バケツリレー」を1行に置き換えられます。
この記事では、まず1ファイルでそのまま動く最小の TypeScript コードを示し、バケツリレー版と並べて違いを確認します。続いて、再レンダリングの回数を実際に数えた結果を表にし、value のメモ化・Context 分割・children で渡す方法でどこまで減るかを比べます。React 18 と React 19 の書き分け、Provider の外で使ったときに気づけるカスタムフックも用意しました。掲載した出力は、React 19.3.0 と 18.3.1、TypeScript 7.0.2、Node.js 22.22.2 と jsdom 30.1.1 で動かしたものです。
useContextの早見表
| やりたいこと | 書き方 | ポイント |
|---|---|---|
| Context を作る | const ThemeContext = createContext<Theme>("light") |
引数は Provider が無いときに使われる初期値 |
| 値を配る(React 19) | <ThemeContext value={theme}> で囲む |
Context をそのまま Provider として書ける |
| 値を配る(React 18 以前) | <ThemeContext.Provider value={theme}> で囲む |
React 19 でもそのまま動く |
| 値を読む | const theme = useContext(ThemeContext) |
コンポーネントの一番上で呼ぶ |
| if の中で読む(React 19) | const theme = use(ThemeContext) |
use は条件分岐やループの中でも呼べる |
| 更新もできるようにする | value に { theme, toggle } のように値と関数を入れる |
状態そのものは useState で持つ |
| Provider の外で使ったら気づきたい | 初期値を null にして、カスタムフックで throw |
型から null も外せる |
| 関係ない再レンダリングを減らす | value を useMemo で包む/Context を分ける/children で渡す |
下の実測表を参照 |
| 一部だけ別の値にする | 内側にもう1つ Provider を置く | 一番近い Provider の値が使われる |
覚えることは「作る・配る・読む」の3つです。 残りの行は、型を安全にする工夫と、再レンダリングを抑える工夫に分かれます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「React useContextの使い方|再レンダリング対策まで」
最小コード:作る・配る・読むの3ステップ
まずは動くものを見るのが近道です。次のコードは1ファイルで完結していて、App.tsx にそのまま貼れます。
import { createContext, useContext, useState } from "react";
type Theme = "light" | "dark";
// 1. 作る:型と初期値(Provider が無いときに使われる値)を渡す
const ThemeContext = createContext<Theme>("light");
// 3. 読む:どれだけ深い階層でも useContext で受け取れる
function Label() {
const theme = useContext(ThemeContext);
return <p>テーマ: {theme}</p>;
}
// 途中のコンポーネントは theme を知らなくてよい
function Panel() {
return (
<section>
<Label />
</section>
);
}
// 2. 配る:Provider の value に渡した値が、内側のすべてに届く
export default function App() {
const [theme, setTheme] = useState<Theme>("dark");
return (
<ThemeContext.Provider value={theme}>
<Panel />
<button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>切り替え</button>
</ThemeContext.Provider>
);
}
描画された HTML と、ボタンを押したあとの HTML です(React 19.3.0。18.3.1 でも同じ結果でした)。
初回表示 : <section><p>テーマ: dark</p></section><button>切り替え</button>
ボタンを押す : <section><p>テーマ: light</p></section><button>切り替え</button>
Panel は theme を一切受け取っていないのに、その内側の Label に値が届いています。 これが useContext の役割です。
3つの登場人物の役割
| 登場人物 | 役割 | 書く場所 |
|---|---|---|
| createContext | 値の通り道を作る。型と初期値を決める | コンポーネントの外(モジュールの先頭) |
| Provider | 通り道に実際の値を流す。囲んだ範囲が配布先になる | 値を持っている親コンポーネント |
| useContext | 一番近い Provider の値を読む | 値を使いたいコンポーネント |
流れを図にすると次のようになります。
flowchart TD
A["App(useState で theme を持つ)"] -->|"Provider の value に渡す"| P["ThemeContext の Provider"]
P --> B["Panel(theme を知らない)"]
B --> C["Label(useContext で読む)"]
P -.->|"途中を飛ばして届く"| C
Providerで囲まなかったときは初期値になる
Provider で囲まずに Panel だけを描画すると、createContext に渡した初期値が使われます。
Provider なし : <section><p>テーマ: light</p></section>
エラーにはならず、初期値の light が表示されました。「値が切り替わらない」と感じたときは、Provider の内側に入っているかを最初に確かめると早く解決できます。囲み忘れをエラーとして知らせたい場合は、後半の「カスタムフック」の形にします。
propsのバケツリレーと並べて比べる
同じ画面を props だけで書くと、次のようになります。
// ビフォー:props のバケツリレー
type Theme = "light" | "dark";
function Label({ theme }: { theme: Theme }) {
return <p>テーマ: {theme}</p>;
}
// Panel 自身は theme を使わないが、下へ渡すためだけに受け取る
function Panel({ theme }: { theme: Theme }) {
return (
<section>
<Label theme={theme} />
</section>
);
}
export default function App() {
return <Panel theme="dark" />;
}
// アフター:useContext
function Label() {
const theme = useContext(ThemeContext);
return <p>テーマ: {theme}</p>;
}
function Panel() {
return (
<section>
<Label />
</section>
);
}
どちらも描画結果は同じ <section><p>テーマ: dark</p></section> でした。違いは「途中の Panel が theme を知っているかどうか」です。
| 観点 | props のバケツリレー | useContext |
|---|---|---|
| 途中のコンポーネント | 使わない値も型定義して受け渡す | 何も書かなくてよい |
| 階層が1つ増えたとき | 増えた階層にも props を追加する | 変更なし |
| 値の出どころ | 親をたどれば必ず見つかる | Provider を探す必要がある |
| 単体での再利用 | props を渡せばどこでも動く | Provider の内側でだけ期待どおり動く |
| 向いている値 | そのコンポーネント固有の値 | テーマ・ログイン中の利用者・言語設定など広く使う値 |
2階層くらいまでなら props のほうが見通しがよく、3階層以上を素通りする値が出てきたら useContext を検討する、という目安が扱いやすいです。props には「どこから来た値かが明確」という良さがあるので、すべてを Context に移す必要はありません。
値と更新関数をまとめて配る完成形
実際のアプリでは、値だけでなく「値を変える関数」も一緒に配りたくなります。次のコードは、Provider・カスタムフック・使う側を1ファイルにまとめた完成形です(React 19 の書き方)。
import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from "react";
type Theme = "light" | "dark";
type ThemeValue = { theme: Theme; toggle: () => void };
// 1. 作る(初期値は null にして「Provider の外」を見分けられるようにする)
const ThemeContext = createContext<ThemeValue | null>(null);
// 2. 配る(状態を持つ Provider を1つのコンポーネントにまとめる)
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const toggle = useCallback(() => setTheme((t) => (t === "light" ? "dark" : "light")), []);
const value = useMemo(() => ({ theme, toggle }), [theme, toggle]);
return <ThemeContext value={value}>{children}</ThemeContext>; // React 18 は <ThemeContext.Provider value={value}>
}
// 3. 読む(null チェックをカスタムフックに閉じ込める)
export function useTheme(): ThemeValue {
const ctx = useContext(ThemeContext);
if (ctx === null) {
throw new Error("useTheme は ThemeProvider の内側で使ってください");
}
return ctx;
}
// 使う側
function ThemeLabel() {
const { theme } = useTheme();
return <p>現在のテーマ: {theme}</p>;
}
function ToggleButton() {
const { toggle } = useTheme();
return <button onClick={toggle}>切り替え</button>;
}
export default function App() {
return (
<ThemeProvider>
<ThemeLabel />
<ToggleButton />
</ThemeProvider>
);
}
実行結果です。
初回表示 : <p>現在のテーマ: light</p><button>切り替え</button>
ボタンを押す : <p>現在のテーマ: dark</p><button>切り替え</button>
この形にしておくと、使う側は useTheme() を呼ぶだけになり、Context そのものを import する必要がなくなります。Context 本体は export せず、Provider とカスタムフックだけを公開すると、読み方が1通りにそろいます。
Providerの外で使うとどうなるか:カスタムフックで気づく
初期値を null にした Context を、チェックなしで読むとどうなるかを確かめました。
import { createContext, useContext } from "react";
type Auth = { user: string };
const AuthContext = createContext<Auth | null>(null);
export function UserName() {
const auth = useContext(AuthContext);
return <span>{auth.user}</span>;
}
tsc --noEmit(TypeScript 7.0.2、strict)の出力です。
art/c1_err.tsx(6,17): error TS18047: 'auth' is possibly 'null'.
型チェックの段階で「null かもしれない」と教えてくれます。 ここで auth!.user や as で型だけ合わせて Provider の外で描画すると、今度は実行時に次のエラーになりました。
Cannot read properties of null (reading 'user')
このメッセージからは、原因が Provider の囲み忘れだとは読み取りにくいです。そこで、null チェックをカスタムフックにまとめます。
function useAuth(): Auth {
const ctx = useContext(AuthContext);
if (ctx === null) {
throw new Error("useAuth は AuthProvider の内側で使ってください");
}
return ctx;
}
const UserName = () => <span>{useAuth().user}</span>;
| 描画のしかた | 結果 |
|---|---|
| Provider の内側で UserName を描画 | <span>sato</span> |
| Provider の外で UserName を描画 | useAuth は AuthProvider の内側で使ってください というエラー |
カスタムフックにすると、エラー文がそのまま直し方になり、戻り値の型からも null が外れます。 使う側で毎回 null チェックを書く必要がなくなるのも利点です。
createContextの初期値で迷ったときの選び方
| 初期値の決め方 | 書き方 | 向いている場面 |
|---|---|---|
| 意味のある初期値を入れる | createContext<Theme>("light") |
Provider が無くても動いてほしい値(テーマ、言語など) |
| null を入れてフックで検査 | createContext<Auth | null>(null) |
Provider が必須の値(ログイン情報、更新関数を含む値) |
なお、React 19 の型定義(@types/react 19.3.0)では初期値の引数が必須です。引数なし、または型に合わない値を渡したときの tsc の出力は次のとおりでした。
art/c1_err.tsx(9,19): error TS2554: Expected 1 arguments, but got 0.
art/c1_err.tsx(10,40): error TS2345: Argument of type '"blue"' is not assignable to parameter of type 'Theme'.
再レンダリングを実測する
useContext で読んでいるコンポーネントは、Provider の value が前回と別の値(Object.is で比べて異なる値)になるたびに再レンダリングされます。 ここを数字で確かめます。
計測に使ったコード
各コンポーネントの先頭に console.log を置き、「親を更新(テーマと無関係な state を変える)」と「テーマを切り替える」の2つの操作で何が再レンダリングされるかを見ます。
import { createContext, useContext, useState } from "react";
type Theme = "light" | "dark";
type ThemeValue = { theme: Theme; toggle: () => void };
const ThemeContext = createContext<ThemeValue | null>(null);
function Header() {
console.log("render: Header"); // Context を読まない
return <h1>設定</h1>;
}
function ThemeLabel() {
console.log("render: ThemeLabel"); // theme を読む
return <p>{useContext(ThemeContext)!.theme}</p>;
}
function ToggleButton() {
console.log("render: ToggleButton"); // toggle だけを読む
const { toggle } = useContext(ThemeContext)!;
return <button id="toggle" onClick={toggle}>切替</button>;
}
export default function App() {
const [theme, setTheme] = useState<Theme>("light");
const [tick, setTick] = useState(0); // テーマと無関係な state
const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));
return (
<ThemeContext.Provider value={{ theme, toggle }}>
<Header />
<ThemeLabel />
<ToggleButton />
<button id="tick" onClick={() => setTick(tick + 1)}>親を更新 {tick}</button>
</ThemeContext.Provider>
);
}
実行結果(React 19.3.0、StrictMode なし)です。
--- 初回表示
render: Header
render: ThemeLabel
render: ToggleButton
--- 「親を更新」をクリック
render: Header
render: ThemeLabel
render: ToggleButton
--- 「切替」をクリック
render: Header
render: ThemeLabel
render: ToggleButton
テーマと無関係な state を変えただけでも、3つすべてが再レンダリングされました。 理由は2つ重なっています。App が再レンダリングされると子も再レンダリングされること、そして value={{ theme, toggle }} が毎回新しいオブジェクトになることです。
6通りの書き方を比べた実測表
同じ3つのコンポーネントで書き方だけを変え、操作1回あたりの再レンダリング回数を数えました。数字は「Header / ThemeLabel / ToggleButton」の順です(React 19.3.0。18.3.1 でも同じ回数でした)。
| 番号 | 書き方 | 親を更新 | テーマ切替 | 合計 |
|---|---|---|---|---|
| 0 | 何もしない(value にオブジェクト直書き) | 1 / 1 / 1 | 1 / 1 / 1 | 6 |
| 1 | 3つを memo で包むだけ(value は直書きのまま) |
0 / 1 / 1 | 0 / 1 / 1 | 4 |
| 2 | memo + value を useMemo |
0 / 0 / 0 | 0 / 1 / 1 | 2 |
| 3 | memo + Context を値用と関数用に分割 |
0 / 0 / 0 | 0 / 1 / 0 | 1 |
| 4 | children で渡す Provider(memo なし・value 直書き) | 1 / 1 / 1 | 0 / 1 / 1 | 5 |
| 5 | children で渡す Provider + Context 分割(memo なし) | 1 / 1 / 1 | 0 / 1 / 0 | 4 |
| 6 | 5 に加えて、Provider の中身を memo |
0 / 0 / 0 | 0 / 1 / 0 | 1 |
番号4〜6の「親を更新」は、Provider コンポーネントのさらに外側にある親が再レンダリングされた場合です。
この表から読み取れることを整理します。
- memo だけでは Context の読み手は止まりません(番号1)。 value が毎回新しいオブジェクトなので、読んでいる2つは再レンダリングされます。
- value を useMemo で包むと、無関係な更新では0回になります(番号2)。 memo と useMemo はセットで効きます。
- theme を読まない ToggleButton まで止めたいときは、Context を分けます(番号3)。 1つの Context に入れた値は、一部だけ読んでいても全体の変化で再レンダリングされるためです。
- children で渡す形にすると、memo を書かなくても、テーマ切替時に Context を読まない Header が再レンダリングされません(番号4)。
- 番号3と番号6は同じ回数です。どちらを選ぶかは、後述の判断表を参考にしてください。
対策1:valueをuseMemoで包む
const toggle = useCallback(() => setTheme((t) => (t === "light" ? "dark" : "light")), []);
const value = useMemo(() => ({ theme, toggle }), [theme, toggle]);
return (
<ThemeContext.Provider value={value}>
<Header />
<ThemeLabel />
<ToggleButton />
</ThemeContext.Provider>
);
value に入れる関数も useCallback で固定しておくのがポイントです。関数が毎回作り直されると、useMemo の依存配列が毎回変わり、メモ化の効果がなくなります。なお、value が文字列や数値だけ(value={theme})なら、同じ値は同じと判定されるので useMemo は不要です。
対策2:Contextを値用と更新関数用に分ける
const ThemeContext = createContext<Theme>("light");
const ThemeToggleContext = createContext<() => void>(() => {});
function ThemeLabel() {
return <p>{useContext(ThemeContext)}</p>; // theme が変わったら再レンダリング
}
function ToggleButton() {
const toggle = useContext(ThemeToggleContext); // toggle は変わらないので再レンダリングされない
return <button onClick={toggle}>切替</button>;
}
function App() {
const [theme, setTheme] = useState<Theme>("light");
const toggle = useCallback(() => setTheme((t) => (t === "light" ? "dark" : "light")), []);
return (
<ThemeContext.Provider value={theme}>
<ThemeToggleContext.Provider value={toggle}>
<Header />
<ThemeLabel />
<ToggleButton />
</ThemeToggleContext.Provider>
</ThemeContext.Provider>
);
}
「よく変わる値」と「変わらない関数」を別の Context にすると、関数だけを使うボタン類が再レンダリングの対象から外れます。更新頻度の違う値(たとえばログイン中の利用者と、入力中の検索語)を分けるときにも同じ考え方が使えます。
対策3:Providerを別コンポーネントにしてchildrenで渡す
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));
return <ThemeContext.Provider value={{ theme, toggle }}>{children}</ThemeContext.Provider>;
}
function Page() {
return (
<>
<Header />
<ThemeLabel />
<ToggleButton />
</>
);
}
export default function Root() {
return (
<ThemeProvider>
<Page />
</ThemeProvider>
);
}
テーマを切り替えると ThemeProvider は再レンダリングされますが、children は親(Root)が作った要素がそのまま渡ってくるだけなので、React は中身を作り直しません。そのうえで、Context を読んでいる ThemeLabel と ToggleButton だけが再レンダリングされます。memo を1つも書かずに Header を対象から外せるのが、この形の良いところです。
どの対策を選ぶかの判断表
| 状況 | 選ぶ対策 | 理由 |
|---|---|---|
| まず1つだけやるなら | Provider を別コンポーネントにして children で渡す | memo なしで、読んでいないコンポーネントが対象から外れる |
| value がオブジェクトや配列 | useMemo(関数は useCallback) |
無関係な更新で value が変わらなくなる |
| 更新関数だけを使うコンポーネントが多い | Context を値用と関数用に分ける | 関数だけの読み手が再レンダリングされなくなる |
| 入力中の文字など、高い頻度で変わる値 | Context に入れず、近くの useState か状態管理ライブラリ | 読み手すべてが毎回再レンダリングされるため |
| value が文字列・数値・真偽値だけ | そのままでよい | 同じ値なら同じと判定される |
再レンダリングは回数が多いこと自体が問題なのではなく、重い描画が繰り返されると体感に影響する、という性質のものです。 まずは console.log で数え、気になるところから手を入れる順番で十分です。React Compiler を使うと memo や useMemo 相当の最適化が自動で入りますが、今回の計測は React Compiler なしで行っており、Compiler ありの回数は未実行です。
React 18とReact 19の書き分け
React 19 では、Context の周りの書き方が2つ増えました。react.dev の createContext と use のリファレンスを今回開いて確認し、あわせて両方のバージョンで動かしました。
| 項目 | React 18.3.1 | React 19.3.0 |
|---|---|---|
<ThemeContext.Provider value={...}> |
動く | 動く(警告も出なかった) |
<ThemeContext value={...}>(直書き) |
エラーになる | 動く |
useContext(ThemeContext) |
動く | 動く |
use(ThemeContext) |
use が存在しない(typeof use は undefined) |
動く。if の中でも呼べる |
<ThemeContext.Consumer> |
動く | 動く(新しく書くコードは useContext がおすすめ) |
Contextをそのまま Provider として書く(React 19)
// React 19
<ThemeContext value="dark">
<Label />
</ThemeContext>
// React 18 以前(React 19 でも動く)
<ThemeContext.Provider value="dark">
<Label />
</ThemeContext.Provider>
React 19.3.0 ではどちらも <p>dark</p> と描画されました。同じ直書きを React 18.3.1 で動かすと、次の警告とエラーになります。
Warning: Rendering <Context> directly is not supported and will be removed in a future major release. Did you mean to render <Context.Consumer> instead?
Warning: A context consumer was rendered with multiple children, or a child that isn't a function. ...
TypeError: render2 is not a function
React 18 では、Context を直接書くと Consumer として扱われるため、子が関数でないというエラーになります(render2 はバンドルツールが付けた内部の関数名です)。型の面でも、@types/react 18.3 では次のエラーになりました。
t/x.tsx(3,25): error TS2786: 'ThemeContext' cannot be used as a JSX component.
Its type 'Context<string>' is not a valid JSX element type.
React 18 と 19 の両方で動かすライブラリやコードは .Provider を付けた書き方に、React 19 以降だけを対象にするなら直書きに、と使い分けるとすっきりします。
useで読む(React 19)
use は React 19 で加わった API で、Context も読めます。useContext との違いは、if やループの中でも呼べる点です。
import { use } from "react";
function Divider({ show }: { show: boolean }) {
if (show) {
const theme = use(ThemeContext); // 早期リターンの後や if の中でも呼べる
return <hr className={theme} />;
}
return null;
}
show が true : <hr class="dark">
show が false : (何も描画されない)
| 観点 | useContext | use |
|---|---|---|
| 使えるバージョン | 16.8 以降 | 19 以降 |
| 呼べる場所 | コンポーネントやフックの一番上 | コンポーネントやフックの中なら、if やループの中でも可 |
| 読めるもの | Context | Context と Promise |
| 選び方 | 迷ったらこちら。React 18 でも動く | 条件によって読む・読まないが変わるとき |
React 18.3.1 で use を import して呼ぶと、use2 is not a function(use2 はバンドルツールが付けた内部名)というエラーになりました。use はサーバーコンポーネントでの Context 読み取りには対応していない点も、公式リファレンスに書かれています。
つまずきやすい点と直し方
valueを書き忘れる
Provider に value を書かずに描画すると、読み手には undefined が届き、コンソールに次の警告が出ました(React 19.3.0)。
The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?
values や val のようなつづり違いでも同じ警告になります。初期値が使われるのは「Provider が無いとき」だけで、「Provider はあるが value が無いとき」は undefined になる点が見落としやすいところです。
同じコンポーネントの中のProviderは読めない
useContext が探すのは、呼び出したコンポーネントより上にある Provider です。同じコンポーネントの return の中に書いた Provider の値は、そのコンポーネント自身の useContext には届きません。
function App() {
const theme = useContext(ThemeContext); // ここで読めるのは、App より上の Provider の値(無ければ初期値)
return (
<ThemeContext.Provider value="dark">
<Label /> {/* dark が届くのはここから下 */}
</ThemeContext.Provider>
);
}
Providerは入れ子にして上書きできる
<ThemeContext.Provider value="dark">
<Label />
<ThemeContext.Provider value="light">
<Label />
</ThemeContext.Provider>
</ThemeContext.Provider>
<p>dark</p><p>light</p>
読み手から見て一番近い Provider の値が使われます。 画面の一部だけ配色を変える、といった使い方ができます。
チェックリスト
| 症状 | 確認すること |
|---|---|
| 初期値のまま変わらない | 読み手が Provider の内側にあるか。Context を別々のファイルで2回 createContext していないか |
| undefined になる | Provider に value を書いたか(つづりも確認) |
is possibly 'null' の型エラー |
カスタムフックで null チェックをまとめる |
| 関係ない操作で再レンダリングされる | value がオブジェクトの直書きになっていないか |
| 関数だけ使うボタンも再レンダリングされる | Context を値用と関数用に分ける |
| React 18 で直書きの Provider がエラーになる | .Provider を付ける |
useContextと他の方法の使い分け
| 方法 | 向いている場面 | メモ |
|---|---|---|
| props | 親子1〜2階層の受け渡し、そのコンポーネント固有の値 | 値の出どころが最も追いやすい |
| children で組み立てる | 途中のコンポーネントがレイアウトだけを担当する | 中身を親で組み立てて渡せば、途中を素通りさせずに済む |
| useContext | テーマ・言語・ログイン中の利用者など、広い範囲で読む値 | 変わる頻度が低い値と相性がよい |
| 状態管理ライブラリ | 高い頻度で変わる値、必要な部分だけを選んで読みたいとき | 読みたい部分だけを購読できる仕組みを持つものが多い |
useContext は「値を配る仕組み」で、状態を持つのは useState や useReducer の役目です。状態は useState、配るのは Context と分けて考えると、コードの置き場所に迷いにくくなります。
よくある質問
Q. useContext を使えば状態管理ライブラリは不要になりますか? A. テーマやログイン情報のように変わる頻度が低い値なら、useState と useContext の組み合わせで十分に組めます。入力のたびに変わる値や、巨大なオブジェクトの一部だけを読みたい場合は、読み手すべてが再レンダリングされる性質があるため、状態管理ライブラリのほうが扱いやすくなります。
Q. Context はいくつ作ってもよいですか? A. はい。テーマ用、ログイン用、のように関心ごとで分けるのがおすすめです。今回の計測でも、値用と関数用に分けることで再レンダリングが1回まで減りました。Provider の入れ子が深くなったら、複数の Provider をまとめるコンポーネントを1つ用意すると読みやすくなります。
Q. React 19 に上げたら .Provider は書き直す必要がありますか?
A. 今回 React 19.3.0 で動かした範囲では、.Provider を付けた書き方もそのまま動き、警告も出ませんでした。公式リファレンスでは React 19 以降は Context を直接 Provider として書けると案内されているので、新しく書く部分から直書きにしていく進め方で問題ありません。
Q. useContext を if の中で呼んでもよいですか?
A. useContext はフックなので、コンポーネントの一番上で呼ぶのがルールです。条件によって読み分けたいときは、React 19 の use を使うと if の中でも呼べます。React 18 では、一番上で読んでから if で使い分ける形にします。
まとめ
- useContext は「作る(createContext)・配る(Provider)・読む(useContext)」の3ステップで、props のバケツリレーを置き換えられる
- Provider で囲まないと初期値、value を書き忘れると undefined になる。初期値を null にしたカスタムフックでエラーにすると原因がすぐ分かる
- value にオブジェクトを直書きすると、無関係な更新でも読み手が再レンダリングされる(実測で6回 → useMemo で2回 → Context 分割で1回)
- Provider を別コンポーネントにして children で渡すと、memo なしでも Context を読まないコンポーネントが対象から外れる
- React 19 は Context の直書きと use が使える。React 18 でも動かすなら
.Providerと useContext を使う - 状態は useState、配るのは Context。 高い頻度で変わる値は近くの state や状態管理ライブラリに任せる
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「React useContextの使い方|再レンダリング対策まで」
株式会社RJCでは、React の useContext や再レンダリングの仕組みを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。