useReducerとuseContextだけでReduxと同じ仕組みを作成する方法

投稿日:2023/06/15 最終更新日:2023/06/15

useReducerとuseContextだけでReduxと同じ仕組みを作成する方法

useContext

useContextとは?

ReactのuseContextフックは、コンポーネント間でデータを共有するために使用されます。

コンポーネントツリーの上位で定義された値(コンテキスト)を、下位のコンポーネントで取得することができます。

これにより、データのプロパゲーションを簡素化し、コンポーネント間の通信をより効率的に行うことができます。

useContextの使用例

まず、コンテキストを作成する必要があります。これはReact.createContext()メソッドを使用して行います。

例えば、以下のようなカウンターアプリケーションを考えてみましょう。

// CounterContext.js
import React from 'react';
const CounterContext = React.createContext();
export default CounterContext;

次に、コンテキストを提供する親コンポーネントを作成します。

この親コンポーネントは、コンテキストの値を設定し、子コンポーネントに渡します。

// App.js
import React from 'react';
import CounterContext from './CounterContext';

const App = () => {
  const counter = 0;// カウンターの初期値
  return (
  <CounterContext.Provider value={counter}>
      {/* 子コンポーネント */}
  </CounterContext.Provider>
  );
};

export default App;

次に、コンテキストを利用する子コンポーネントを作成します。

これには、useContextフックを使用します。

// CounterDisplay.js
import React, { useContext } from 'react';
import CounterContext from './CounterContext';

const CounterDisplay = () => {
 const counter = useContext(CounterContext);
 return <div>Counter: {counter}</div>;
};

export default CounterDisplay;

上記の例では、CounterDisplayコンポーネントでCounterContextを利用しています。

useContextフックを使用してCounterContextの値を取得し、コンポーネント内で使用することができます。

最後に、親コンポーネントで子コンポーネントを表示します。

// App.js import React from 'react';
import CounterContext from './CounterContext';
import CounterDisplay from './CounterDisplay';

const App = () => {
 const counter = 0;
 // カウンターの初期値
 return (
  <CounterContext.Provider value={counter}>
   <CounterDisplay />
  </CounterContext.Provider>
 );
};

export default App;

これでCounterDisplayコンポーネントが親コンポーネントから提供されたカウンターの値を表示することができます。

useContextフックを使用することで、コンポーネント間でのデータの受け渡しや共有が容易になります。

複雑なデータフローを持つアプリケーションや、多層のコンポーネントツリーでのデータの受け渡しに特に便利です。

useReducer

useReducerとは?

ReactのuseReducerフックは、状態の管理と状態の変更を行うために使用されます。

useStateフックとは異なり、複数の状態を一つのオブジェクトにまとめることができます。

また、useReducerフックはアクションに基づいて状態を変更することができるため、複雑な状態の変更ロジックをより簡潔に表現することができます。

useReducerの使用例

まず、初期状態と状態を変更するための関数(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');
  }
};

// 初期状態の定義
const initialState = { count: 0 };

次に、useReducerフックを使用して状態とディスパッチ関数を取得します。

const [state, dispatch] = useReducer(reducer, initialState);

stateは現在の状態を表し、dispatchはアクションを発行するための関数です。

最後に、アクションをディスパッチして状態を変更するコードを書きます。

<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<p>Count: {state.count}</p>

上記の例では、ボタンをクリックすることでアクションがディスパッチされ、reducer関数に基づいて状態が変更されます。

そして、変更後の状態が画面上に表示されます。

useReducerフックは、複雑な状態管理や状態変更ロジックが必要な場面で特に役立ちます。

状態の変更が他の状態に依存する場合や、複数のアクションが複雑に絡み合う場合に有用です。

useContext×useReducer

まず、状態と状態変更のためのreducer関数を定義します。

例として、ユーザーの名前と年齢を管理するアプリケーションを考えます。

// reducer関数の定義
const reducer = (state, action) => {
  switch (action.type) {
    case 'setUserName':
      return { ...state, name: action.payload };
    case 'setUserAge':
      return { ...state, age: action.payload };
    default:
      throw new Error('Unknown action');
  }
};

// 初期状態の定義
const initialState = { name: '', age: 0 };

次に、Contextオブジェクトを作成します。

このContextオブジェクトは、他のコンポーネントで使用するためにエクスポートします。

// Contextオブジェクトの作成
const UserContext = React.createContext();

続いて、Context Providerコンポーネントを作成します。

このコンポーネントでは、useReducerフックを使用して状態とディスパッチ関数を取得し、Context Providerの値として提供します。

// Context Providerコンポーネントの作成
const UserProvider = ({ children }) => {
  const [state, dispatch] = React.useReducer(reducer, initialState);

  return (
  <UserContext.Provider value={{ state, dispatch }}>
      {children}
  </UserContext.Provider>
  );
};

ここで、UserProviderコンポーネントは子要素をラップするためのコンポーネントであり、その子要素には状態とディスパッチ関数が提供されます。

最後に、Contextを使用するコンポーネントを作成します。

useContextフックを使用してContextから状態とディスパッチ関数を取得し、必要に応じて利用します。

// Contextを使用するコンポーネントの作成
const UserInfo = () => {
  const { state, dispatch } = React.useContext(UserContext);

  const handleNameChange = (e) => {
    dispatch({ type: 'setUserName', payload: e.target.value });
  };

  const handleAgeChange = (e) => {
    dispatch({ type: 'setUserAge', payload: parseInt(e.target.value, 10) });
  };

  return (
  <div>
   <input type="text" value={state.name} onChange={handleNameChange} />
   <input type="number" value={state.age} onChange={handleAgeChange} />
  </div>
 );
};

上記の例では、UserContextから状態とディスパッチ関数を取得し、それぞれの入力フィールドの値の変更時に適切なアクションをディスパッチしています。

最後に、アプリケーションのエントリーポイントでUserProviderコンポーネントを使用してContextを提供します。

ReactDOM.render(
  <UserProvider>
  <UserInfo />
 </UserProvider>,
  document.getElementById('root')
);

これで、useContextとuseReducerを組み合わせた状態管理が完了しました。

UserInfoコンポーネント内でContextから状態とディスパッチ関数を使用して、ユーザーの名前と年齢を変更することができます。

参考資料

React公式ドキュメント – useContext

React Context API チュートリアル

React Context API の使い方

React公式ドキュメント – useReducer

React Hooks 入門 – useReducerで状態を更新する

React HooksのuseReducerで状態管理を楽にする