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;
解説
State
とAction
の型定義: 状態とアクションの型を明確にする。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;
解説
AppState
とAppAction
: 状態とアクションの拡張。- Payloadの型定義: アクションに必要なデータ構造を明示。