Reactでの状態管理は、アプリケーション開発において重要な課題の一つです。特にReduxは、複雑な状態管理を効率化するために長年利用されてきました。しかし、Reduxの導入には学習コストやコードの冗長性といった課題もあります。そこで注目されているのが、Reactの標準機能であるContext APIとHooksの組み合わせです。これらを活用することで、Reduxを使わずにシンプルかつ柔軟な状態管理を実現することが可能です。本記事では、ContextとHooksの基本から実践的な応用例まで、初心者にもわかりやすく解説します。Reactでの開発効率を高めるヒントを得られるでしょう。
Reduxの課題とContext+Hooksの利点
Reduxは、Reactアプリケーションにおける状態管理の標準的な選択肢として広く採用されてきました。しかし、その強力な機能の裏にはいくつかの課題が存在します。
Reduxの課題
学習コストの高さ
Reduxの導入には、独自の概念や設計パターン(例:Action、Reducer、Store)を学ぶ必要があります。初学者にとっては敷居が高く感じられることも少なくありません。
コードの冗長性
Reduxでは、単純な状態管理でも多数のファイルやコード行を必要とします。Actionの定義、Reducerの作成、Storeの構成など、初期設定だけでも多くのコードが必要です。
パフォーマンスの問題
Reduxを適切に設定しない場合、不要な再レンダリングが発生することがあります。これはアプリケーションのパフォーマンスに影響を与える原因となります。
Context+Hooksの利点
Context APIとHooksを組み合わせることで、Reduxの課題を解決しつつシンプルな状態管理を実現できます。
シンプルな実装
Reduxのような外部ライブラリを使わずに、Reactの標準機能のみで状態管理を実現できます。特に、Context APIを使ってグローバルな状態を共有し、useContextフックで簡単にアクセスできます。
コード量の削減
Reduxと比較して、Context+Hooksを用いることで必要なコード量を大幅に削減できます。React開発者にとって直感的な構造を持ち、特別な設定も不要です。
柔軟性の向上
useStateやuseReducerフックを組み合わせることで、アプリケーションの要件に応じた柔軟な状態管理が可能です。
学習コストの低減
Reactの標準機能だけを使用するため、追加のライブラリや設計パターンを学ぶ必要がなく、React初心者にも取り組みやすい選択肢です。
Context+Hooksの活用場面
Context+Hooksは、以下のようなシナリオで特に効果を発揮します:
- 小規模または中規模のアプリケーション
- 状態の依存関係が比較的少ないシンプルな構造
- Reduxの複雑性を避けつつグローバルな状態を管理したい場合
これらの特徴から、Context+HooksはモダンなReact開発において、状態管理の有力な選択肢となっています。
Context APIの基本的な使い方
Context APIは、Reactでコンポーネント間で状態やデータを共有するための仕組みです。グローバルな状態管理が必要な場合、Contextを利用することで親子間のデータの受け渡しを簡素化できます。以下では、Contextの作成から利用までの基本的な使い方を解説します。
Contextの作成
ContextはReact.createContext
を用いて作成します。作成されたContextは、状態や値を供給するProvider
と、値を受け取るConsumer
を提供します。
import React, { createContext } from "react";
// Contextの作成
const MyContext = createContext();
export default MyContext;
Context.Providerを使った値の供給
Contextを使用するには、Provider
を使って値を供給します。このProvider
は、通常アプリケーションの上位コンポーネントに配置します。
import React from "react";
import MyContext from "./MyContext";
const App = () => {
return (
<MyContext.Provider value={{ user: "John Doe", isLoggedIn: true }}>
<MyComponent />
</MyContext.Provider>
);
};
export default App;
ここで、value
プロパティに渡したオブジェクトが、Contextを利用するコンポーネントでアクセス可能なデータになります。
Context.Consumerを使った値の取得
Consumer
を使用すると、供給された値を取得できます。ただし、より簡便なuseContext
フックが登場して以降、この方法はあまり使われなくなりました。
import React from "react";
import MyContext from "./MyContext";
const MyComponent = () => {
return (
<MyContext.Consumer>
{({ user, isLoggedIn }) => (
<div>
<p>User: {user}</p>
<p>Logged in: {isLoggedIn ? "Yes" : "No"}</p>
</div>
)}
</MyContext.Consumer>
);
};
export default MyComponent;
Contextの適用範囲
Contextの値は、Provider
内の全ての子コンポーネントに適用されます。これにより、親コンポーネントから複数の階層をまたいでデータを渡す「プロップスドリル」を回避できます。
注意点
- 過剰なContextの利用は、再レンダリングの負荷を増加させる場合があります。状態を細かく分けて適切に管理しましょう。
- 状態が複雑になる場合は、useReducerや複数のContextを組み合わせることが推奨されます。
次のセクションでは、useContext
フックを用いた効率的な値の取得方法について詳しく解説します。
useContextフックの活用方法
useContext
フックは、ReactでContextの値を取得するための簡便な方法を提供します。このフックを利用することで、従来のConsumer
コンポーネントを使用するよりも、簡潔なコードでContextの値を操作することができます。
useContextの基本構文
useContext
フックを使用するには、まずContextをインポートし、そのContextを引数として渡します。
import React, { useContext } from "react";
import MyContext from "./MyContext";
const MyComponent = () => {
const contextValue = useContext(MyContext); // Contextの値を取得
return (
<div>
<p>User: {contextValue.user}</p>
<p>Logged in: {contextValue.isLoggedIn ? "Yes" : "No"}</p>
</div>
);
};
export default MyComponent;
この方法では、必要な値を直接取得でき、コードの可読性が向上します。
useContextを使った状態管理の具体例
以下に、簡単な状態管理の例として、ユーザー認証状態を管理するケースを示します。
Contextの作成
import React, { createContext } from "react";
const AuthContext = createContext(); // 認証用のContextを作成
export default AuthContext;
Providerによる値の供給
import React, { useState } from "react";
import AuthContext from "./AuthContext";
const AuthProvider = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const login = () => setIsLoggedIn(true);
const logout = () => setIsLoggedIn(false);
return (
<AuthContext.Provider value={{ isLoggedIn, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export default AuthProvider;
useContextで値を取得
子コンポーネントでuseContext
を使用して値を取得し、認証状態を操作します。
import React from "react";
import AuthContext from "./AuthContext";
const UserStatus = () => {
const { isLoggedIn, login, logout } = useContext(AuthContext);
return (
<div>
<p>Status: {isLoggedIn ? "Logged In" : "Logged Out"}</p>
{isLoggedIn ? (
<button onClick={logout}>Logout</button>
) : (
<button onClick={login}>Login</button>
)}
</div>
);
};
export default UserStatus;
useContextの利点
- 簡潔なコード: コンポーネントのネストを避け、必要な値を直接取得できます。
- 再利用性: 関数型コンポーネント内で簡単に呼び出すことができ、再利用性が向上します。
- シンプルなデバッグ: 値を取得する処理が直感的で、デバッグが容易です。
注意点
- Contextの値が更新されると、
useContext
を使用している全てのコンポーネントが再レンダリングされます。パフォーマンス最適化が必要な場合は、Contextを分割するか、React.memoやuseMemoを活用してください。
このように、useContext
はContextの値を活用するための便利なツールです。次のセクションでは、状態管理を効率化するためにuseReducer
と組み合わせる方法について解説します。
状態管理にuseReducerを組み合わせる利点
useReducer
フックは、複雑な状態管理において特に有効です。useReducer
をContext
と組み合わせることで、状態遷移のロジックを分離し、より予測可能で管理しやすいコードを実現できます。このセクションでは、useReducer
の基本と、そのContextとの統合について解説します。
useReducerの基本構文
useReducer
は、状態とその更新ロジックを一元管理します。useReducer
は次のように構成されます:
const [state, dispatch] = useReducer(reducer, initialState);
- reducer: 状態遷移を管理する関数
- initialState: 状態の初期値
- dispatch: アクションをトリガーする関数
reducer関数の例
以下は、簡単なカウンターを管理するreducer関数の例です:
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error("Unknown action");
}
};
useReducerを使った状態管理
カウンターの状態管理を例に、useReducer
を実装してみます。
import React, { useReducer } from "react";
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error("Unknown action");
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>Increment</button>
<button onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
</div>
);
};
export default Counter;
Contextとの組み合わせ
useReducer
をContext
と組み合わせることで、グローバルな状態管理をより効率的に行えます。
ContextとReducerのセットアップ
Context
を作成し、useReducer
の値をProvider
で供給します。
import React, { createContext, useReducer } from "react";
const CounterContext = createContext();
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error("Unknown action");
}
};
const CounterProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
);
};
export { CounterContext, CounterProvider };
値の取得と操作
子コンポーネントでuseContext
を使い、状態を操作します。
import React, { useContext } from "react";
import { CounterContext } from "./CounterProvider";
const Counter = () => {
const { state, dispatch } = useContext(CounterContext);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>Increment</button>
<button onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
</div>
);
};
export default Counter;
useReducerを組み合わせる利点
- 複雑な状態管理に対応: 状態遷移を明確に定義し、大規模なアプリケーションでも管理が容易。
- コードの分離: reducer関数を用いることで、ビジネスロジックをコンポーネントから分離。
- 一貫性のある状態遷移: dispatchで全ての状態変更を行うため、コードが一貫して予測可能。
次のセクションでは、これらの知識を基にした実践例として、Todoアプリを実装します。
状態管理の実践例:Todoアプリ
Context APIとHooksを使用して、簡単なTodoアプリを実装する方法を解説します。このアプリでは、以下の機能を実現します:
- Todoの追加
- Todoの削除
- Todoの完了ステータスの切り替え
全体構成
このアプリは以下の3つの主要コンポーネントで構成されます:
- TodoProvider: 状態を管理し、他のコンポーネントに供給するContext。
- TodoList: Todoの一覧を表示するコンポーネント。
- AddTodo: 新しいTodoを追加するコンポーネント。
ContextとReducerの作成
Reducerの定義
Todoの状態遷移を管理するreducer
関数を作成します。
const todoReducer = (state, action) => {
switch (action.type) {
case "add":
return [...state, { id: Date.now(), text: action.text, completed: false }];
case "toggle":
return state.map(todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
case "delete":
return state.filter(todo => todo.id !== action.id);
default:
throw new Error("Unknown action type");
}
};
Contextの定義
Todo管理用のContext
を作成します。
import React, { createContext, useReducer } from "react";
const TodoContext = createContext();
const TodoProvider = ({ children }) => {
const [todos, dispatch] = useReducer(todoReducer, []);
return (
<TodoContext.Provider value={{ todos, dispatch }}>
{children}
</TodoContext.Provider>
);
};
export { TodoContext, TodoProvider };
Todo一覧表示コンポーネント
TodoList
は、Todo一覧を表示し、各Todoの削除と完了ステータスの切り替えを提供します。
import React, { useContext } from "react";
import { TodoContext } from "./TodoProvider";
const TodoList = () => {
const { todos, dispatch } = useContext(TodoContext);
return (
<ul>
{todos.map(todo => (
<li key={todo.id} style={{ textDecoration: todo.completed ? "line-through" : "none" }}>
<span onClick={() => dispatch({ type: "toggle", id: todo.id })}>
{todo.text}
</span>
<button onClick={() => dispatch({ type: "delete", id: todo.id })}>Delete</button>
</li>
))}
</ul>
);
};
export default TodoList;
Todo追加コンポーネント
新しいTodoを追加するための入力フォームを提供します。
import React, { useState, useContext } from "react";
import { TodoContext } from "./TodoProvider";
const AddTodo = () => {
const [text, setText] = useState("");
const { dispatch } = useContext(TodoContext);
const handleSubmit = e => {
e.preventDefault();
if (text.trim()) {
dispatch({ type: "add", text });
setText("");
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={e => setText(e.target.value)}
placeholder="Add a new todo"
/>
<button type="submit">Add</button>
</form>
);
};
export default AddTodo;
アプリの統合
TodoProvider
をルートに配置し、他のコンポーネントをラップします。
import React from "react";
import { TodoProvider } from "./TodoProvider";
import TodoList from "./TodoList";
import AddTodo from "./AddTodo";
const App = () => {
return (
<TodoProvider>
<h1>Todo App</h1>
<AddTodo />
<TodoList />
</TodoProvider>
);
};
export default App;
動作確認
このアプリでは、以下の操作が可能です:
- 新しいTodoの追加: テキストを入力し「Add」を押すと、リストに追加されます。
- 完了ステータスの切り替え: Todoをクリックすると、線が引かれ「完了」状態になります。
- Todoの削除: 「Delete」ボタンを押すと、Todoが削除されます。
解説と利点
- ContextとReducerを組み合わせることで、グローバルな状態管理を簡素化できます。
- useReducerを使用することで、状態遷移ロジックが明確になり、拡張が容易です。
- 小規模から中規模のアプリケーションに最適な構造となっています。
次のセクションでは、さらに複雑な状態管理のケーススタディを解説します。
複雑な状態管理のケーススタディ
より大規模なReactアプリケーションでは、状態が複数の種類に分かれたり、コンポーネント間で深くネストされた構造を持つ場合があります。このセクションでは、複数のContextやネスト構造を用いて、複雑な状態管理を実現する方法を解説します。
ケーススタディ: チャットアプリ
チャットアプリを例に、以下の状態を管理します:
- ユーザー認証情報: ユーザーがログインしているかどうかの情報。
- チャットルームデータ: 現在のチャットルームとそのメッセージ。
- 通知設定: ユーザーが設定した通知のオン/オフ状態。
Contextの設計
複数のContextを分離することで、必要な状態を必要な場所でのみ管理します。これにより、無駄な再レンダリングを防ぎます。
1. 認証情報のContext
import React, { createContext, useReducer } from "react";
const AuthContext = createContext();
const authReducer = (state, action) => {
switch (action.type) {
case "login":
return { ...state, isLoggedIn: true, user: action.user };
case "logout":
return { ...state, isLoggedIn: false, user: null };
default:
throw new Error("Unknown action type");
}
};
const AuthProvider = ({ children }) => {
const [authState, dispatch] = useReducer(authReducer, { isLoggedIn: false, user: null });
return (
<AuthContext.Provider value={{ authState, dispatch }}>
{children}
</AuthContext.Provider>
);
};
export { AuthContext, AuthProvider };
2. チャットルームのContext
const ChatContext = createContext();
const chatReducer = (state, action) => {
switch (action.type) {
case "setRoom":
return { ...state, currentRoom: action.room, messages: [] };
case "addMessage":
return { ...state, messages: [...state.messages, action.message] };
default:
throw new Error("Unknown action type");
}
};
const ChatProvider = ({ children }) => {
const [chatState, dispatch] = useReducer(chatReducer, { currentRoom: null, messages: [] });
return (
<ChatContext.Provider value={{ chatState, dispatch }}>
{children}
</ChatContext.Provider>
);
};
export { ChatContext, ChatProvider };
3. 通知設定のContext
const NotificationContext = createContext();
const NotificationProvider = ({ children }) => {
const [isNotificationEnabled, setNotification] = React.useState(true);
return (
<NotificationContext.Provider value={{ isNotificationEnabled, setNotification }}>
{children}
</NotificationContext.Provider>
);
};
export { NotificationContext, NotificationProvider };
Providerの統合
全てのProviderをアプリケーションのルートでラップします。
import React from "react";
import { AuthProvider } from "./AuthProvider";
import { ChatProvider } from "./ChatProvider";
import { NotificationProvider } from "./NotificationProvider";
const AppProviders = ({ children }) => {
return (
<AuthProvider>
<ChatProvider>
<NotificationProvider>{children}</NotificationProvider>
</ChatProvider>
</AuthProvider>
);
};
export default AppProviders;
コンポーネントでの利用例
ユーザーのログイン状態を表示
import React, { useContext } from "react";
import { AuthContext } from "./AuthProvider";
const UserStatus = () => {
const { authState, dispatch } = useContext(AuthContext);
return (
<div>
{authState.isLoggedIn ? (
<p>Welcome, {authState.user.name}</p>
) : (
<p>Please log in</p>
)}
<button
onClick={() =>
authState.isLoggedIn
? dispatch({ type: "logout" })
: dispatch({ type: "login", user: { name: "John Doe" } })
}
>
{authState.isLoggedIn ? "Logout" : "Login"}
</button>
</div>
);
};
export default UserStatus;
チャットメッセージの追加
import React, { useContext } from "react";
import { ChatContext } from "./ChatProvider";
const ChatRoom = () => {
const { chatState, dispatch } = useContext(ChatContext);
return (
<div>
<h2>Room: {chatState.currentRoom || "No room selected"}</h2>
<button onClick={() => dispatch({ type: "setRoom", room: "General" })}>
Join General
</button>
<ul>
{chatState.messages.map((msg, idx) => (
<li key={idx}>{msg}</li>
))}
</ul>
<button
onClick={() =>
dispatch({ type: "addMessage", message: "Hello from " + chatState.currentRoom })
}
>
Send Message
</button>
</div>
);
};
export default ChatRoom;
利点と課題
- 利点:
- 状態を適切に分割して管理することで、再レンダリングを最小限に抑制。
- 各Contextが独立しており、拡張や変更が容易。
- useContextにより、状態へのアクセスが簡単。
- 課題:
- Contextが増えると、コードが複雑化するため、構造を明確に設計する必要がある。
このように複数のContextを利用することで、複雑なアプリケーションにおいても柔軟で効率的な状態管理を実現できます。次のセクションでは、パフォーマンス最適化のベストプラクティスを解説します。
パフォーマンス最適化のベストプラクティス
ReactでContextとHooksを利用した状態管理は便利ですが、適切なパフォーマンス最適化を行わないと、無駄な再レンダリングが発生する可能性があります。このセクションでは、パフォーマンスを向上させるためのベストプラクティスを解説します。
再レンダリングを最小限に抑える方法
Contextの分割
1つのContextに多くのデータを詰め込みすぎると、どのデータが変更されても、Contextを利用する全てのコンポーネントが再レンダリングされます。以下の方法でContextを分割すると、影響範囲を限定できます。
悪い例: 全ての状態を1つのContextにまとめる
const AppContext = createContext({ user: null, theme: "light", notifications: [] });
良い例: 状態ごとにContextを分割
const UserContext = createContext(null);
const ThemeContext = createContext("light");
const NotificationContext = createContext([]);
これにより、必要な状態のみを更新し、それに依存するコンポーネントだけが再レンダリングされるようになります。
React.memoの活用
React.memo
を使用することで、親コンポーネントが再レンダリングされた際に、子コンポーネントの再レンダリングを防ぐことができます。
import React, { memo } from "react";
const TodoItem = memo(({ todo, onToggle }) => {
return (
<li onClick={() => onToggle(todo.id)} style={{ textDecoration: todo.completed ? "line-through" : "none" }}>
{todo.text}
</li>
);
});
これにより、todo
やonToggle
が変更されない限り、TodoItem
は再レンダリングされません。
useMemoとuseCallbackの活用
計算コストの高い処理や関数をメモ化することで、不要な再計算や再生成を防ぎます。
useMemoの例:
const sortedTodos = useMemo(() => {
return todos.sort((a, b) => a.text.localeCompare(b.text));
}, [todos]);
useCallbackの例:
const handleToggle = useCallback((id) => {
dispatch({ type: "toggle", id });
}, [dispatch]);
これにより、sortedTodos
やhandleToggle
が依存する値が変わらない限り、同じ値や関数を再利用します。
デバッグツールの活用
React DevTools
React DevToolsを使用すると、コンポーネントの再レンダリングの頻度を確認できます。
- 「Highlight updates when components render」オプションを有効にして、どのコンポーネントが再レンダリングされているかを視覚的に確認できます。
why-did-you-renderライブラリ
このライブラリを使用すると、不必要な再レンダリングを検出できます。
npm install @welldone-software/why-did-you-render
設定例:
import React from "react";
import whyDidYouRender from "@welldone-software/why-did-you-render";
if (process.env.NODE_ENV === "development") {
whyDidYouRender(React);
}
const MyComponent = ({ prop }) => {
return <div>{prop}</div>;
};
MyComponent.whyDidYouRender = true;
export default MyComponent;
リストレンダリングの最適化
キーの適切な設定
リストレンダリングでは、ユニークなキーを使用してReactに要素の識別を正確にさせます。
悪い例:
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li>
))}
良い例:
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
負荷が高い処理の分割
コード分割(Code Splitting)
Reactの動的インポートを使用して、必要なコンポーネントのみを遅延読み込みします。
import React, { Suspense } from "react";
const HeavyComponent = React.lazy(() => import("./HeavyComponent"));
const App = () => (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
まとめ
- Contextの分割で再レンダリングの影響範囲を最小化。
- React.memo、useMemo、useCallbackで不要な再レンダリングや再計算を防止。
- デバッグツールで問題箇所を特定し改善。
- リストレンダリングのキー設定やコード分割で効率を向上。
これらの最適化手法を活用することで、ContextとHooksを用いたReactアプリケーションのパフォーマンスを大幅に改善できます。次のセクションでは、実践的な演習問題と応用例を解説します。
実践的な演習問題と応用例
このセクションでは、ContextとHooksの学習を深めるために、実践的な演習問題を提供します。また、実際の開発における応用例を示し、習得した知識の活用方法を具体的に解説します。
演習問題
問題1: テーマ切り替え機能の実装
以下の仕様に従い、アプリケーションにテーマ切り替え機能を実装してください:
- Contextの作成: テーマ(ライトモードとダークモード)を管理するContextを作成。
- テーマの切り替え: ボタンを押すと、テーマが切り替わるようにする。
- 背景色の変更: 現在のテーマに応じて背景色が変更される。
ヒント: useState
フックを使用し、Contextで状態を管理します。
解答例:
import React, { createContext, useState, useContext } from "react";
const ThemeContext = createContext();
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
const ThemeToggle = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div style={{ background: theme === "light" ? "#fff" : "#333", color: theme === "light" ? "#000" : "#fff", height: "100vh" }}>
<p>Current Theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
};
const App = () => (
<ThemeProvider>
<ThemeToggle />
</ThemeProvider>
);
export default App;
問題2: グローバルカウンターの実装
- ContextとReducerを使用して、グローバルに管理されるカウンターを実装してください。
- カウンターを増減するボタンを用意し、現在の値を表示します。
- 複数のコンポーネントでカウンターを共有します。
応用例
応用例1: 認証管理
大規模なアプリケーションでは、認証機能をグローバルに管理する必要があります。ContextとHooksを使えば、以下のような実装が可能です:
- ログイン状態を管理するContextを作成。
- ログイン時にユーザー情報を保存し、全コンポーネントからアクセス可能にする。
- 認証済みユーザーのみが特定のルートにアクセスできるように制御する。
コード例:
import React, { createContext, useContext, useState } from "react";
const AuthContext = createContext();
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const login = userInfo => setUser(userInfo);
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
};
const ProtectedRoute = ({ children }) => {
const { user } = useContext(AuthContext);
return user ? children : <p>Please log in to access this page.</p>;
};
const App = () => (
<AuthProvider>
<ProtectedRoute>
<p>Welcome to the protected page!</p>
</ProtectedRoute>
</AuthProvider>
);
export default App;
応用例2: 商品リストフィルタリング
eコマースアプリで、商品のカテゴリや価格帯、評価に基づいてリストを動的にフィルタリングする機能をContextで実装します。
- Contextを利用してフィルタリング条件をグローバルに管理。
- useReducerで複数の条件を効率的に管理。
演習のメリット
- ContextとHooksを使った状態管理をより深く理解できる。
- 実務でよく使われるシナリオを体験できる。
- パフォーマンスやコードの分離に配慮した設計を学べる。
これらの演習問題と応用例を通じて、ContextとHooksの応用力を磨きましょう。次のセクションでは、記事全体のまとめを行います。
まとめ
本記事では、ReactのContextとHooksを使用した状態管理について、基礎から実践的な応用例まで詳しく解説しました。Reduxを代替する方法として、ContextとHooksはコードの簡潔さや学習コストの低さで優れています。また、useReducer
を組み合わせることで、複雑な状態管理にも対応可能です。
さらに、パフォーマンス最適化や具体的な演習問題を通じて、実際の開発で直面する課題への対処法を学びました。
- Contextの分割やReact.memoによる再レンダリング抑制
- useMemoやuseCallbackを活用した効率的な計算
- 認証管理やフィルタリングなど、実務での応用例
これらの知識を活用することで、小規模から中規模のReactアプリケーションでの状態管理を効率的に行うことができます。今後は、プロジェクトでContextとHooksを積極的に試し、そのメリットを実感してください。React開発のさらなる効率化が期待できるでしょう。
コメント