8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事では、React のコンポーネント階層が深くなったときに発生する「prop drilling」を解決するための useContext の使い方と設計上の注意点を解説します。実用的なコード例とパフォーマンス対策を示します。

1. 階層深すぎて引数追えなくなってませんか?

こんなことになっていませんか?

  • 親コンポーネントで状態(例: user)を持っているのに、実際にその状態を使うのは深い子コンポーネントだけになっている
  • 中間のコンポーネントは状態を扱わないのに、ただ受け渡しのためだけに props が増えている
  • コンポーネントの props が肥大化して、差分や影響範囲が追いにくくなっている

例えば以下のようなパターンです。

// 親コンポーネント
function Parent() {
  const [user, setUser] = useState({ name: "yuta" });
  return <A user={user} />;
}

function A({ user }) {
  return <B user={user} />;
}
function B({ user }) {
  return <C user={user} />;
}
function C({ user }) {
  return <DeepChild user={user} />;
}
function DeepChild({ user }) {
  return <div>{user.name}</div>;
}

このように、中間の A / B / Cuser を使わないのに単に受け渡しているだけ、という状態が「prop drilling」です。可読性・保守性の観点から問題になりやすいため、次節以降で useContext を使った改善方法を紹介します。

2. useContext の基本的な使い方

Context を定義して Provider でラップすることで、どの深さのコンポーネントからでも値を参照できます。基本的な例は次のとおりです。

// AppContext.js
import React, { createContext, useContext, useState } from "react";

const AppContext = createContext(null);

export const AppProvider = ({ children }) => {
  const [user, setUser] = useState({ name: "yuta" });
  const [theme, setTheme] = useState("light");

  const value = { user, setUser, theme, setTheme };

  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};

export const useApp = () => {
  const ctx = useContext(AppContext);
  if (!ctx) throw new Error("useApp must be used within AppProvider");
  return ctx;
};

上記を導入すると、深い階層のコンポーネントで次のように参照できます。

import { useApp } from "./AppContext";

function DeepChild() {
  const { user } = useApp();
  return <div>こんにちは、{user.name}</div>;
}

3. 実務で注意すべき点

  • Provider の value が毎回新しい参照になると、Context を参照するすべての子コンポーネントが再レンダリングされます。
    • 対策: useMemouseCallback を使って value をメモ化する、あるいは状態を分割して複数の Context を用意してください。
const value = useMemo(() => ({ user, setUser }), [user]);
  • 状態の分割: 更新頻度が異なる値(例: テーマとユーザー情報)は別々の Context にすることで不要な再レンダリングを減らせます。

  • 更新ロジックが複雑な場合は useReducer を使ってロジックをまとめると、保守性が向上します。

4. 設計パターン例

  • スモールグローバル(テーマ、ロケール、認証情報): Context が有用です
  • 頻繁に変わる大量の UI 状態: Context のみで処理するとパフォーマンス問題になるため、ローカル state や専用の状態管理ライブラリを検討してください

5. 深い階層での導入例

構成例: AppLayoutSidebarMenuMenuItem

App が持つ userMenuItem に渡す場合、Context を使えば中間コンポーネントを変更する必要がありません。

// index.jsx
import React from "react";
import { createRoot } from "react-dom/client";
import { AppProvider } from "./AppContext";
import App from "./App";

createRoot(document.getElementById("root")).render(
  <AppProvider>
    <App />
  </AppProvider>
);

6. パフォーマンス改善のポイント

  • Context を用途ごとに分割する
  • Provider の valueuseMemo でメモ化する
  • Context を直接参照するコンポーネントを小さく分割する
  • 高度な対策: use-context-selector のようなライブラリを利用すると、セレクタ単位で再レンダリングを最小化できます

7. 実践的なサンプル

// UserContext.js
import React, { createContext, useContext, useState, useMemo } from "react";

const UserContext = createContext(null);
export const UserProvider = ({ children }) => {
  const [user, setUser] = useState({ name: "yuta" });
  const value = useMemo(() => ({ user, setUser }), [user]);
  return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};

export const useUser = () => {
  const ctx = useContext(UserContext);
  if (!ctx) throw new Error("useUser must be used within UserProvider");
  return ctx;
};

// DeepChild.jsx
import React from "react";
import { useUser } from "./UserContext";

export default function DeepChild() {
  const { user } = useUser();
  return <span>{user.name}</span>;
}

このパターンにより中間コンポーネントは props を透過的に扱えるため、変更範囲が限定されます。

8. まとめ

  • Prop drilling は可読性と保守性の低下を招きます
  • useContext は有効な解決手段ですが、乱用すると再レンダリングの原因になります
  • useMemo、Context 分割、useReducer、あるいは専用ライブラリを組み合わせて使うと良いです
8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?