【TypeScript】React状態管理の型安全な実装パターン - 状態管理のベストプラクティス

【TypeScript】React状態管理の型安全な実装パターン - 状態管理のベストプラクティス

2024-11-10

2024-11-10

概要

Reactでの状態管理は、アプリケーションの規模が大きくなるにつれて複雑になります。さらに、TypeScriptを使用することで型安全性を確保し、より保守性の高いコードを実現できます。状態管理を型安全に実装するためには、状態とアクションに対して厳密な型定義を行い、誤ったデータ操作を未然に防ぐ必要があります。本記事では、TypeScriptで型安全にReactの状態管理を実装するパターンについて、Context APIReduxZustandといった代表的な手法を紹介します。

React Context APIによる型安全な状態管理

ReactのContext APIは、アプリケーションの中でグローバルな状態管理を行うのに適した方法です。TypeScriptとContext APIを組み合わせることで、プロパティやアクションの型が保証され、誤った状態操作が防げます。

実装ステップ

以下に、カウンターアプリケーションを例に、Context APIを使った型安全な実装を行います。

状態とアクションの型定義

まず、状態とアクションの型を定義します。

type State = {
  count: number;
};
type Action = 
  | { type: "increment" }
  | { type: "decrement" };

ここで、State型はカウンターの値を表すcountプロパティのみを持ち、Action型にはincrementまたはdecrementのアクションが定義されています。

Reducer関数の定義

次に、状態を更新するreducer関数を定義し、アクションに基づいて型安全に状態を操作します。

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      return state;
  }
}

Contextの作成とProviderコンポーネント

ContextとProviderコンポーネントを定義し、子コンポーネントに型安全な状態とディスパッチ関数を提供します。

import React, { createContext, useContext, useReducer } from "react";
const CounterContext = createContext<{
  state: State;
  dispatch: React.Dispatch<Action>;
} | undefined>(undefined);
const CounterProvider: React.FC = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <CounterContext.Provider value={{ state, dispatch }}>
      {children}
    </CounterContext.Provider>
  );
};

useCounterフックの作成

Contextの型を簡潔に扱えるよう、カスタムフックを作成します。

function useCounter() {
  const context = useContext(CounterContext);
  if (!context) {
    throw new Error("useCounter must be used within a CounterProvider");
  }
  return context;
}

コンポーネントでの使用

最後に、作成したContextとカスタムフックを利用して、状態を取得し、型安全に操作します。

const Counter = () => {
  const { state, dispatch } = useCounter();
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>Increment</button>
      <button onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
    </div>
  );
};
export const App = () => (
  <CounterProvider>
    <Counter />
  </CounterProvider>
);

この実装により、Context APIで状態管理を行いつつ、TypeScriptによって型安全性が保証されます。

Reduxによる型安全な状態管理

Reduxは大規模アプリケーション向けの状態管理ライブラリです。TypeScriptと組み合わせることで、アクションやステートに厳密な型を適用し、型安全なグローバル状態管理が可能になります。

Redux ToolkitとTypeScriptの組み合わせ

Redux Toolkitは、Reduxの公式ツールセットで、設定が簡略化され、TypeScriptとの相性も良好です。Redux Toolkitを使用した型安全な実装例を見てみましょう。

Redux Toolkitのインストール

Redux ToolkitとRedux用のTypeScript型パッケージをインストールします。

npm install @reduxjs/toolkit react-redux

Sliceの定義

Redux ToolkitのcreateSliceを使って、スライスを定義し、アクションとリデューサーを一括で定義します。

import { createSlice, PayloadAction, configureStore } from "@reduxjs/toolkit";
type CounterState = {
  count: number;
};
const initialState: CounterState = { count: 0 };
const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.count += 1;
    },
    decrement: (state) => {
      state.count -= 1;
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.count += action.payload;
    },
  },
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
const store = configureStore({
  reducer: { counter: counterSlice.reducer },
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;

counterSlice内で状態とアクションの型を定義することで、Reducer関数内の状態操作が型安全に行えます。

useSelectorとuseDispatchの型付け

ReduxのuseSelectoruseDispatchを型安全に使用するためのカスタムフックを作成します。

import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

コンポーネントでの使用

カスタムフックを使用して、状態やアクションを型安全に操作します。

import React from "react";
import { increment, decrement, incrementByAmount } from "./counterSlice";
import { useAppDispatch, useAppSelector } from "./hooks";
const Counter = () => {
  const count = useAppSelector((state) => state.counter.count);
  const dispatch = useAppDispatch();
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
      <button onClick={() => dispatch(incrementByAmount(5))}>
Increment by 5</button>
    </div>
  );
};

Redux ToolkitとTypeScriptの組み合わせにより、Reduxの状態管理が型安全に行えます。

Zustandによる型安全な状態管理

Zustandは軽量で柔軟な状態管理ライブラリで、Reduxよりも設定が少なく、Reactのローカル状態の代替として利用できます。

Zustandのインストールと基本設定

まず、Zustandをインストールします。

npm install zustand

Zustandストアの作成

Zustandのストアを作成し、状態とアクションの型を定義します。

import create from "zustand";
type CounterState = {
  count: number;
  increment: () => void;
  decrement: () => void;
};
const useCounterStore = create<CounterState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

コンポーネントでの使用

Zustandのストアから状態とアクションを取得して使用します。

const Counter = () => {
  const { count, increment, decrement } = useCounterStore();
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

ZustandのシンプルなAPIにより、最小限のコードで型安全な状態管理が実現できます。

まとめ

TypeScriptを使ったReactの状態管理は、型安全性を確保することでエラーを未然に防ぎ、開発効率と保守性を向上させます。Context API、Redux Toolkit、Zustandなど、それぞれの用途やプロジェクト規模に応じて適切なライブラリを選び、型安全な状態管理を実現しましょう。

Recommend