Skip to content

ContextとState管理

公開日:December 8, 2024更新日:December 8, 2024
ReactTypeScriptCoding📄

ReactでのState管理にTypeScriptを活用することで、型安全性を保ちながらスケーラブルなアプリケーションを構築できます。この章では、Context APIの型付け、useReducerとの組み合わせ、型安全なState管理の方法について解説します。

React Context APIの型付け

React Context APIは、コンポーネントツリー全体にデータを提供するために使用されます。TypeScriptを使うことで、Contextの値に型を付け、予期しないエラーを防ぐことができます。

基本例

以下は、簡単なテーマ設定のContextを型付けする例です。

tsx
import React, { createContext, useContext, useState, ReactNode } from 'react';

interface ThemeContextType {
  theme: string;
  setTheme: (theme: string) => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

const ThemeProvider = ({ children }: { children: ReactNode }) => {
  const [theme, setTheme] = useState<string>('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

const useTheme = () => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
};

const App = () => {
  const { theme, setTheme } = useTheme();

  return (
    <div>
      <p>Current theme: {theme}</p>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </div>
  );
};

export default () => (
  <ThemeProvider>
    <App />
  </ThemeProvider>
);

解説

  • createContextの型引数: Contextの値の型を明確にする。
  • useContextの型保証: 必要な場合にContextが未定義であるエラーを防止。

useReducerとTypeScriptの組み合わせ

useReducerフックは複雑なState管理に役立ちます。TypeScriptを使うことで、アクションとStateに型を付け、エラーを防ぎます。

基本例

以下は、カウンターのStateを管理するuseReducerの例です。

tsx
import React, { useReducer } from 'react';

interface State {
  count: number;
}

interface IncrementAction {
  type: 'increment';
}

interface DecrementAction {
  type: 'decrement';
}

interface ResetAction {
  type: 'reset';
}

type Action = IncrementAction | DecrementAction | ResetAction;

const initialState: State = { count: 0 };

const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return initialState;
    default:
      throw new Error('Unknown action type');
  }
};

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

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
};

export default Counter;

解説

  • StateActionの型定義: 状態とアクションの型を明確にする。
  • reducerの型安全性: 型を指定することで、未定義のアクションタイプを防ぐ。

型安全なState管理

複雑なState管理でも、型を用いることで安全性を保つことができます。

拡張されたState管理

次の例は、複数のプロパティを持つStateを管理する例です。

tsx
interface AppState {
  user: { name: string; age: number } | null;
  isAuthenticated: boolean;
}

const initialState: AppState = {
  user: null,
  isAuthenticated: false,
};

interface LoginAction {
  type: 'login';
  payload: { name: string; age: number };
}

interface LogoutAction {
  type: 'logout';
}

type AppAction = LoginAction | LogoutAction;

const appReducer = (state: AppState, action: AppAction): AppState => {
  switch (action.type) {
    case 'login':
      return {
        ...state,
        user: action.payload,
        isAuthenticated: true,
      };
    case 'logout':
      return initialState;
    default:
      throw new Error('Unknown action type');
  }
};

const App = () => {
  const [state, dispatch] = useReducer(appReducer, initialState);

  return (
    <div>
      {state.isAuthenticated ? (
        <div>
          <p>Welcome, {state.user?.name}</p>
          <button onClick={() => dispatch({ type: 'logout' })}>Logout</button>
        </div>
      ) : (
        <button
          onClick={() =>
            dispatch({
              type: 'login',
              payload: { name: 'John Doe', age: 30 },
            })
          }
        >
          Login
        </button>
      )}
    </div>
  );
};

export default App;

解説

  • AppStateAppAction: 状態とアクションの拡張。
  • Payloadの型定義: アクションに必要なデータ構造を明示。