Redux不要!ReactのContextとHooksで状態管理を簡単に実現する方法

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フックは、複雑な状態管理において特に有効です。useReducerContextと組み合わせることで、状態遷移のロジックを分離し、より予測可能で管理しやすいコードを実現できます。このセクションでは、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との組み合わせ


useReducerContextと組み合わせることで、グローバルな状態管理をより効率的に行えます。

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つの主要コンポーネントで構成されます:

  1. TodoProvider: 状態を管理し、他のコンポーネントに供給するContext。
  2. TodoList: Todoの一覧を表示するコンポーネント。
  3. 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>
  );
});

これにより、todoonToggleが変更されない限り、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]);

これにより、sortedTodoshandleToggleが依存する値が変わらない限り、同じ値や関数を再利用します。

デバッグツールの活用

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: テーマ切り替え機能の実装


以下の仕様に従い、アプリケーションにテーマ切り替え機能を実装してください:

  1. Contextの作成: テーマ(ライトモードとダークモード)を管理するContextを作成。
  2. テーマの切り替え: ボタンを押すと、テーマが切り替わるようにする。
  3. 背景色の変更: 現在のテーマに応じて背景色が変更される。

ヒント: 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: グローバルカウンターの実装

  1. ContextとReducerを使用して、グローバルに管理されるカウンターを実装してください。
  2. カウンターを増減するボタンを用意し、現在の値を表示します。
  3. 複数のコンポーネントでカウンターを共有します。

応用例

応用例1: 認証管理


大規模なアプリケーションでは、認証機能をグローバルに管理する必要があります。ContextとHooksを使えば、以下のような実装が可能です:

  1. ログイン状態を管理するContextを作成。
  2. ログイン時にユーザー情報を保存し、全コンポーネントからアクセス可能にする。
  3. 認証済みユーザーのみが特定のルートにアクセスできるように制御する。

コード例:

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開発のさらなる効率化が期待できるでしょう。

コメント

コメントする

目次