ホーム
おまかせ表示
最近の更新
特別ページ
コミュニティ・ポータル
設定
MochiuWiki : SUSE, EC, PCBについて
免責事項
MochiuWiki : SUSE, EC, PCB
検索
利用者メニュー
ログイン
Reactの基礎 - useReducerのソースを表示
←
Reactの基礎 - useReducer
📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは
2026年9月に閉鎖
いたします。
移転先は
https://mochiu.net/index.php?title=Reactの基礎 - useReducer
です。
新しい記事は移転先で追加しております。
(旧サイトでは記事を追加しておりません)
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == <code>useReducer</code> は、複雑な状態ロジックを管理するためのReact Hookである。<br> 基本構文は <code>const [state, dispatch] = useReducer(reducer, initialState)</code> であり、現在の状態値 <code>state</code> と、状態更新をトリガーする <code>dispatch</code> 関数を返す。<br> <br> <code>reducer</code> は <code>(state, action) => newState</code> という形式の純粋関数であり、現在の状態とアクションを受け取り、新しい状態を返す。<br> この設計はReduxパターンに基づいており、状態遷移が予測可能で、テストしやすい構造になっている。<br> <br> <code>useReducer</code> と <code>useState</code> は用途によって使い分ける。<br> 単純な値や独立した状態には <code>useState</code> が適しているが、複雑なオブジェクト・配列の状態管理、複数の関連する状態の一元管理、多くのアクション型が存在する場合には <code>useReducer</code> が適している。<br> 目安として、<code>useState</code> を3個以上使用している場合は <code>useReducer</code> への移行を検討するとよい。<br> <br> <code>useReducer</code> を使用することにより、状態更新ロジックをコンポーネントの外部に分離でき、コードの見通しが改善される。<br> また、<code>dispatch</code> 関数は再レンダリング間で安定した参照を持つため、<code>useCallback</code> でラップする必要がなく、子コンポーネントへの受け渡しにも適している。<br> <br> TypeScriptとの相性も良く、アクション型にDiscriminated Union (判別付きユニオン) を使用することで、<code>switch</code> 文内で型の絞り込みが自動的に行われる。<br> <br> 第3引数として初期化関数を渡すことで、遅延初期化 (Lazy Initialization) も可能であり、初期状態の計算コストが高い場合に有用である。<br> <br><br> == 基本的な使用方法 == ==== Reducer関数の定義 ==== <code>reducer</code> 関数は、<code>(state, action) => newState</code> という形式の純粋関数として定義する。<br> switch文を使用してアクションの <code>type</code> に応じた処理に分岐し、常に新しい状態オブジェクトを返す必要がある。<br> <br> <syntaxhighlight lang="typescript"> import { useReducer } from 'react'; // 状態の型定義 interface CounterState { count: number; } // アクションの型定義 type CounterAction = | { type: 'INCREMENT' } | { type: 'DECREMENT' } | { type: 'RESET' } | { type: 'SET'; payload: number }; // Reducer関数 : 純粋関数として定義する function counterReducer(state: CounterState, action: CounterAction): CounterState { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; case 'RESET': return { count: 0 }; case 'SET': return { count: action.payload }; default: return state; } } // 初期状態 const initialState: CounterState = { count: 0 }; // コンポーネント function Counter() { const [state, dispatch] = useReducer(counterReducer, initialState); return ( <div> <p>カウント: {state.count}</p> <button onClick={() => dispatch({ type: 'INCREMENT' })}>増加</button> <button onClick={() => dispatch({ type: 'DECREMENT' })}>減少</button> <button onClick={() => dispatch({ type: 'RESET' })}>リセット</button> <button onClick={() => dispatch({ type: 'SET', payload: 10 })}>10に設定</button> </div> ); } </syntaxhighlight> <br> ==== dispatch関数 ==== <code>dispatch</code> 関数は、<code>reducer</code> に渡すアクションオブジェクトを引数に取る。<br> アクションオブジェクトには <code>type</code> プロパティが必須であり、追加データを渡す場合は慣例として <code>payload</code> プロパティを使用する。<br> <br> <syntaxhighlight lang="typescript"> // type のみのアクション (ペイロードなし) dispatch({ type: 'INCREMENT' }); dispatch({ type: 'RESET' }); // payload を持つアクション dispatch({ type: 'SET', payload: 42 }); dispatch({ type: 'SET_NAME', payload: 'Alice' }); </syntaxhighlight> <br> <code>dispatch</code> は同期的に処理されるため、<code>dispatch</code> 呼び出し直後に <code>state</code> を参照しても、更新後の値は得られない点に注意する。<br> 更新後の状態が反映されるのは次のレンダリング以降である。<br> <br><br> == TypeScriptでの型定義 == ==== Discriminated Union型 ==== TypeScriptでは、アクション型をDiscriminated Union (判別可能なユニオン型) として定義することを推奨する。<br> 各アクションオブジェクトの <code>type</code> プロパティをリテラル型にすることで、TypeScriptが各caseブロック内でアクションの型を自動的に絞り込む。<br> <br> <syntaxhighlight lang="typescript"> // Discriminated Union型によるアクション定義 type Action = | { type: 'INCREMENT' } | { type: 'DECREMENT' } | { type: 'SET_COUNT'; payload: number } | { type: 'SET_NAME'; payload: string } | { type: 'RESET' }; function reducer(state: State, action: Action): State { switch (action.type) { case 'SET_COUNT': // この case ブロック内では action.payload が number 型に絞り込まれる return { ...state, count: action.payload }; case 'SET_NAME': // この case ブロック内では action.payload が string 型に絞り込まれる return { ...state, name: action.payload }; case 'INCREMENT': // この case ブロック内では action に payload プロパティは存在しない return { ...state, count: state.count + 1 }; default: return state; } } </syntaxhighlight> <br> ==== State型の定義 ==== 状態の型は <code>interface</code> または <code>type</code> を使用して定義する。<br> 複雑な状態を持つ場合でも、型定義により状態の構造が明確になる。<br> <br> <syntaxhighlight lang="typescript"> // State型の定義 interface State { count: number; history: number[]; name: string; isLoading: boolean; error: string | null; } // 初期状態 const initialState: State = { count: 0, history: [], name: '', isLoading: false, error: null, }; // アクション型の定義 type Action = | { type: 'INCREMENT' } | { type: 'DECREMENT' } | { type: 'SET_COUNT'; payload: number } | { type: 'SET_NAME'; payload: string } | { type: 'SET_LOADING'; payload: boolean } | { type: 'SET_ERROR'; payload: string | null } | { type: 'RESET' }; function reducer(state: State, action: Action): State { switch (action.type) { case 'INCREMENT': return { ...state, count: state.count + 1, history: [...state.history, state.count + 1], }; case 'DECREMENT': return { ...state, count: state.count - 1, history: [...state.history, state.count - 1], }; case 'SET_COUNT': return { ...state, count: action.payload }; case 'SET_NAME': return { ...state, name: action.payload }; case 'SET_LOADING': return { ...state, isLoading: action.payload }; case 'SET_ERROR': return { ...state, error: action.payload }; case 'RESET': return initialState; default: return state; } } </syntaxhighlight> <br><br> == useReducerとuseStateの使い分け == <code>useReducer</code> と <code>useState</code> はどちらも状態管理に使用するが、それぞれに適したユースケースが異なる。<br> <br> <center> {| class="wikitable" |+ useReducer と useStateの比較 ! 比較項目 !! useState !! useReducer |- | 状態の複雑さ || 単純な値 (数値・文字列・真偽値) || 複雑なオブジェクトや配列 |- | 状態の構造 || 独立した単一の値 || 複数の関連した値をまとめた構造体 |- | アクション数 || 1〜2種類の更新操作 || 多くの種類のアクションが存在する。 |- | 状態の依存性 || 各状態が独立している。 || 次の状態が現在の状態に依存する。 |- | テスト容易性 || コンポーネントと一体 || reducerを単体でテストできる。 |- | ロジックの分離 || コンポーネント内に記述 || コンポーネント外に分離できる。 |- | 推奨規模 || 小規模・単純なコンポーネント || 中〜大規模・複雑な状態管理 |} </center> <br> 下表に、使い分けの判断基準を示す。<br> <br> <center> {| class="wikitable" |+ 使い分けの判断基準 ! 状況 !! 推奨するHook |- | カウンターや入力値など単一の値を管理する場合 || useState |- | 真偽値のトグル操作 || useState |- | 3つ以上の useState が存在し、関連している場合 || useReducer |- | 状態の更新に複数のアクションパターンがある場合 || useReducer |- | 次の状態が現在の状態の複数フィールドに依存する場合 || useReducer |- | 状態更新ロジックをコンポーネント外にテストしたい場合 || useReducer |} </center> <br><br> == 遅延初期化 == <code>useReducer</code> の第3引数に初期化関数 (<code>init</code>) を渡すことにより、遅延初期化を実現できる。<br> 初期化関数は初回レンダリング時にのみ実行されるため、処理コストの高い初期状態の計算を遅延させるのに適している。<br> <br> 構文は <code>useReducer(reducer, initialArg, init)</code> であり、<code>init(initialArg)</code> の戻り値が初期状態として使用される。<br> <br> <syntaxhighlight lang="typescript"> interface State { count: number; items: string[]; } type Action = | { type: 'INCREMENT' } | { type: 'RESET'; payload: string } | { type: 'ADD_ITEM'; payload: string }; // 初期化関数 : 引数を受け取り、初期状態を返す // この関数は初回レンダリング時のみ実行される function createInitialState(userId: string): State { // 処理コストの高い初期化処理 const savedData = localStorage.getItem(`state_${userId}`); if (savedData) { return JSON.parse(savedData) as State; } return { count: 0, items: [] }; } function reducer(state: State, action: Action): State { switch (action.type) { case 'INCREMENT': return { ...state, count: state.count + 1 }; case 'RESET': // init関数を使用してリセット return createInitialState(action.payload); case 'ADD_ITEM': return { ...state, items: [...state.items, action.payload] }; default: return state; } } function UserCounter({ userId }: { userId: string }) { // 第3引数にinit関数を指定 : createInitialState(userId) が初回のみ実行される const [state, dispatch] = useReducer(reducer, userId, createInitialState); return ( <div> <p>カウント: {state.count}</p> <button onClick={() => dispatch({ type: 'INCREMENT' })}>増加</button> <button onClick={() => dispatch({ type: 'RESET', payload: userId })}>リセット</button> </div> ); } </syntaxhighlight> <br> 遅延初期化には、<code>reset</code> アクション時に <code>init</code> 関数を再利用して初期状態に戻すことができるメリットもある。<br> <br><br> == サンプルコード : Reducerパターン == ==== Todoリストの管理 ==== 複数のアクション型を持つTodoリストは、<code>useReducer</code> が効果的に機能するユースケースの代表例である。<br> <br> <syntaxhighlight lang="typescript"> interface Todo { id: number; text: string; completed: boolean; } type FilterType = 'ALL' | 'ACTIVE' | 'COMPLETED'; interface TodoState { todos: Todo[]; filter: FilterType; nextId: number; } type TodoAction = | { type: 'ADD'; payload: string } | { type: 'REMOVE'; payload: number } | { type: 'TOGGLE'; payload: number } | { type: 'SET_FILTER'; payload: FilterType } | { type: 'CLEAR_COMPLETED' }; const initialTodoState: TodoState = { todos: [], filter: 'ALL', nextId: 1, }; function todoReducer(state: TodoState, action: TodoAction): TodoState { switch (action.type) { case 'ADD': return { ...state, todos: [ ...state.todos, { id: state.nextId, text: action.payload, completed: false }, ], nextId: state.nextId + 1, }; case 'REMOVE': return { ...state, todos: state.todos.filter((todo) => todo.id !== action.payload), }; case 'TOGGLE': return { ...state, todos: state.todos.map((todo) => todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo ), }; case 'SET_FILTER': return { ...state, filter: action.payload }; case 'CLEAR_COMPLETED': return { ...state, todos: state.todos.filter((todo) => !todo.completed), }; default: return state; } } function TodoApp() { const [state, dispatch] = useReducer(todoReducer, initialTodoState); const [inputText, setInputText] = useState(''); const filteredTodos = state.todos.filter((todo) => { if (state.filter === 'ACTIVE') return !todo.completed; if (state.filter === 'COMPLETED') return todo.completed; return true; }); return ( <div> <input value={inputText} onChange={(e) => setInputText(e.target.value)} placeholder="新しいTodoを入力" /> <button onClick={() => { if (inputText.trim()) { dispatch({ type: 'ADD', payload: inputText.trim() }); setInputText(''); } }}>追加</button> <div> <button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'ALL' })}>全て</button> <button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'ACTIVE' })}>未完了</button> <button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'COMPLETED' })}>完了</button> </div> <ul> {filteredTodos.map((todo) => ( <li key={todo.id}> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }} onClick={() => dispatch({ type: 'TOGGLE', payload: todo.id })} > {todo.text} </span> <button onClick={() => dispatch({ type: 'REMOVE', payload: todo.id })}>削除</button> </li> ))} </ul> <button onClick={() => dispatch({ type: 'CLEAR_COMPLETED' })}>完了済みを削除</button> </div> ); } </syntaxhighlight> <br> ==== フォーム状態の管理 ==== 複数の入力フィールドを持つフォームの状態を、<code>useReducer</code> で一元管理する例を以下に示す。<br> <br> <syntaxhighlight lang="typescript"> interface FormState { name: string; email: string; password: string; isSubmitting: boolean; errors: Record<string, string>; } type FormAction = | { type: 'SET_FIELD'; field: keyof Pick<FormState, 'name' | 'email' | 'password'>; payload: string } | { type: 'SET_SUBMITTING'; payload: boolean } | { type: 'SET_ERROR'; field: string; payload: string } | { type: 'CLEAR_ERRORS' } | { type: 'RESET' }; const initialFormState: FormState = { name: '', email: '', password: '', isSubmitting: false, errors: {}, }; function formReducer(state: FormState, action: FormAction): FormState { switch (action.type) { case 'SET_FIELD': return { ...state, [action.field]: action.payload }; case 'SET_SUBMITTING': return { ...state, isSubmitting: action.payload }; case 'SET_ERROR': return { ...state, errors: { ...state.errors, [action.field]: action.payload }, }; case 'CLEAR_ERRORS': return { ...state, errors: {} }; case 'RESET': return initialFormState; default: return state; } } function RegistrationForm() { const [state, dispatch] = useReducer(formReducer, initialFormState); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); dispatch({ type: 'CLEAR_ERRORS' }); dispatch({ type: 'SET_SUBMITTING', payload: true }); try { // フォーム送信処理 await submitForm(state); } catch (error) { dispatch({ type: 'SET_ERROR', field: 'submit', payload: '送信に失敗しました' }); } finally { dispatch({ type: 'SET_SUBMITTING', payload: false }); } }; return ( <form onSubmit={handleSubmit}> <input value={state.name} onChange={(e) => dispatch({ type: 'SET_FIELD', field: 'name', payload: e.target.value })} placeholder="名前" /> <input value={state.email} onChange={(e) => dispatch({ type: 'SET_FIELD', field: 'email', payload: e.target.value })} placeholder="メールアドレス" /> <input type="password" value={state.password} onChange={(e) => dispatch({ type: 'SET_FIELD', field: 'password', payload: e.target.value })} placeholder="パスワード" /> {state.errors.submit && <p>{state.errors.submit}</p>} <button type="submit" disabled={state.isSubmitting}> {state.isSubmitting ? '送信中...' : '登録'} </button> </form> ); } </syntaxhighlight> <br><br> == Reduxパターンとの比較 == <code>useReducer</code> はReduxと同じ設計思想に基づいているが、スコープや機能面で重要な違いがある。<br> <br> <center> {| class="wikitable" |+ useReducerとReduxの比較 ! 比較項目 !! useReducer !! Redux (Redux Toolkit) |- | スコープ || 単一コンポーネント (またはContextを通じたサブツリー) || アプリケーション全体 |- | セットアップ || React組み込みのため追加インストール不要 || パッケージのインストールが必要 |- | ミドルウェア || 非対応 || redux-thunk、redux-saga等を使用可能 |- | DevTools || 非対応 || Redux DevToolsで状態遷移を可視化できる。 |- | 非同期処理 || useEffectと組み合わせる必要がある。 || createAsyncThunk等で統合的に処理できる。 |- | 拡張性 || コンポーネントレベルに限定される || プラグインやミドルウェアで拡張できる。 |- | 学習コスト || 低い (React Hooksの知識のみ) || 高い (Redux固有の概念が多い) |- | 適した規模 || 小〜中規模アプリケーション || 中〜大規模アプリケーション |} </center> <br> 下表に、類似点をまとめる。<br> <br> <center> {| class="wikitable" |+ useReducer と Reduxの類似点 ! 共通概念 !! 説明 |- | reducer関数 || どちらも <code>(state, action) => newState</code> の純粋関数を使用する。 |- | アクションオブジェクト || どちらも <code>type</code> プロパティを持つアクションオブジェクトを使用する。 |- | 予測可能な状態遷移 || 同じアクションと状態に対して常に同じ結果を返す。 |- | イミュータブルな状態更新 || 既存の状態を変更せず、新しい状態オブジェクトを返す。 |} </center> <br><br> == useReducer と useContextの組み合わせ == <code>useReducer</code> と <code>useContext</code> を組み合わせることにより、コンポーネントツリー全体でグローバルな状態管理を実現できる。<br> Props drilling (プロップスのバケツリレー) を避けながら、複数のコンポーネントから状態にアクセスできるようになる。<br> <br> 状態と <code>dispatch</code> を分離してContextを定義することで、<code>dispatch</code> のみを使用するコンポーネントの不要な再レンダリングを防ぐことができる。<br> <br> <syntaxhighlight lang="typescript"> import { createContext, useContext, useReducer, ReactNode } from 'react'; // 型定義 interface Task { id: number; text: string; completed: boolean; } interface TaskState { tasks: Task[]; nextId: number; } type TaskAction = | { type: 'ADD'; payload: string } | { type: 'REMOVE'; payload: number } | { type: 'TOGGLE'; payload: number }; // Contextの型定義 (StateとDispatchを分離) const TaskStateContext = createContext<TaskState | null>(null); const TaskDispatchContext = createContext<React.Dispatch<TaskAction> | null>(null); // Reducer function taskReducer(state: TaskState, action: TaskAction): TaskState { switch (action.type) { case 'ADD': return { ...state, tasks: [...state.tasks, { id: state.nextId, text: action.payload, completed: false }], nextId: state.nextId + 1, }; case 'REMOVE': return { ...state, tasks: state.tasks.filter((t) => t.id !== action.payload) }; case 'TOGGLE': return { ...state, tasks: state.tasks.map((t) => t.id === action.payload ? { ...t, completed: !t.completed } : t ), }; default: return state; } } // Providerコンポーネント function TaskProvider({ children }: { children: ReactNode }) { const [state, dispatch] = useReducer(taskReducer, { tasks: [], nextId: 1 }); return ( <TaskStateContext.Provider value={state}> <TaskDispatchContext.Provider value={dispatch}> {children} </TaskDispatchContext.Provider> </TaskStateContext.Provider> ); } // カスタムHook : StateとDispatchを個別に取得 function useTaskState(): TaskState { const context = useContext(TaskStateContext); if (!context) throw new Error('useTaskState must be used within TaskProvider'); return context; } function useTaskDispatch(): React.Dispatch<TaskAction> { const context = useContext(TaskDispatchContext); if (!context) throw new Error('useTaskDispatch must be used within TaskProvider'); return context; } // 使用例 function TaskList() { // 状態のみを購読するため、dispatchが変化しても再レンダリングされない const { tasks } = useTaskState(); const dispatch = useTaskDispatch(); return ( <ul> {tasks.map((task) => ( <li key={task.id}> <span onClick={() => dispatch({ type: 'TOGGLE', payload: task.id })}> {task.text} </span> <button onClick={() => dispatch({ type: 'REMOVE', payload: task.id })}>削除</button> </li> ))} </ul> ); } function App() { return ( <TaskProvider> <TaskList /> </TaskProvider> ); } </syntaxhighlight> <br><br> == Immerライブラリとの組み合わせ == Immerライブラリを使用すると、ネストされた状態の更新をスプレッド演算子なしで簡単に記述できる。<br> <br> Immerの <code>produce</code> 関数内では、状態を直接変更するような記述ができるが、実際には内部でイミュータブルな更新が行われる。<br> <br> <u>Immerを使用する場合は、<code>npm install immer</code> コマンドを実行して、Immerパッケージをインストールする必要がある。</u><br> <br> <syntaxhighlight lang="typescript"> import { useReducer } from 'react'; import { produce } from 'immer'; interface User { id: number; name: string; email: string; address: { city: string; zipCode: string; }; } interface UserState { users: Record<number, User>; selectedId: number | null; } type UserAction = | { type: 'UPDATE_EMAIL'; id: number; payload: string } | { type: 'UPDATE_CITY'; id: number; payload: string } | { type: 'SELECT'; payload: number }; // スプレッド演算子を使用した従来の記述 function reducerWithSpread(state: UserState, action: UserAction): UserState { switch (action.type) { case 'UPDATE_EMAIL': return { ...state, users: { ...state.users, [action.id]: { ...state.users[action.id], email: action.payload, }, }, }; case 'UPDATE_CITY': return { ...state, users: { ...state.users, [action.id]: { ...state.users[action.id], address: { ...state.users[action.id].address, city: action.payload, }, }, }, }; default: return state; } } // Immerを使用した簡潔な記述 const reducerWithImmer = produce((draft: UserState, action: UserAction) => { switch (action.type) { case 'UPDATE_EMAIL': // draft を直接変更するように記述できる draft.users[action.id].email = action.payload; break; case 'UPDATE_CITY': // ネストが深い場合も簡潔に記述できる draft.users[action.id].address.city = action.payload; break; case 'SELECT': draft.selectedId = action.payload; break; } }); function UserManager() { const [state, dispatch] = useReducer(reducerWithImmer, { users: {}, selectedId: null, }); return <div>...</div>; } </syntaxhighlight> <br><br> == よくある間違い == ==== 状態の直接変更 ==== <code>reducer</code> 内で既存の状態オブジェクトを直接変更してそのまま返すと、Reactが変更を検知できず、再レンダリングが発生しない。<br> <u>常に新しいオブジェクトを返す必要がある。</u><br> <br> <syntaxhighlight lang="typescript"> // 正しい : 新しいオブジェクトを返す function goodReducer(state: State, action: Action): State { switch (action.type) { case 'INCREMENT': return { ...state, count: state.count + 1 }; // スプレッドで新しいオブジェクトを作成 case 'ADD_ITEM': return { ...state, items: [...state.items, action.payload] }; // 新しい配列を作成 } } // 誤り : 既存のオブジェクトを直接変更して返している function badReducer(state: State, action: Action): State { switch (action.type) { case 'INCREMENT': state.count += 1; // 直接変更 return state; // 同じオブジェクト参照を返す (変更を検知できない) case 'ADD_ITEM': state.items.push(action.payload); // 配列の直接変更 return state; } } </syntaxhighlight> <br> ==== アクション型の不整合 ==== <code>dispatch</code> に渡すアクションの <code>type</code> 文字列と、<code>reducer</code> の <code>case</code> 文字列が一致しない場合、アクションが処理されない。<br> <u>TypeScriptのDiscriminated Union型を使用することにより、この種のミスをコンパイル時に検知できる。</u><br> <br> <syntaxhighlight lang="typescript"> // 正しい : TypeScriptの型定義で整合性を保証する type Action = { type: 'INCREMENT' } | { type: 'DECREMENT' }; dispatch({ type: 'INCREMENT' }); // 型チェックにより一致が保証される // 誤り : caseの文字列が大文字なのに、dispatchは小文字 dispatch('increment'); // string型で渡している dispatch({ type: 'increment' }); // 小文字 // reducer側 case 'INCREMENT': // 大文字 (一致しない) </syntaxhighlight> <br> ==== Reducer内の副作用 ==== <code>reducer</code> 関数は純粋関数でなければならない。<br> API呼び出し、ローカルストレージへの書き込み、乱数生成、タイマの設定等の副作用を <code>reducer</code> 内に記述してはいけない。<br> <br> <u>副作用は <code>useEffect</code> 内で処理する。</u><br> <br> <syntaxhighlight lang="typescript"> // 誤り : reducer内で副作用を実行している function badReducer(state: State, action: Action): State { switch (action.type) { case 'SAVE': localStorage.setItem('data', JSON.stringify(state)); // 副作用 fetch('/api/save', { method: 'POST', body: JSON.stringify(state) }); // 副作用 return state; case 'ADD': return { ...state, id: Math.random() }; // 乱数生成も副作用 } } // 正しい : 副作用はuseEffectで処理する function goodReducer(state: State, action: Action): State { switch (action.type) { case 'ADD': return { ...state, items: [...state.items, action.payload] }; default: return state; } } function MyComponent() { const [state, dispatch] = useReducer(goodReducer, initialState); // 副作用はuseEffectで処理する useEffect(() => { localStorage.setItem('data', JSON.stringify(state)); }, [state]); return <div>...</div>; } </syntaxhighlight> <br><br> == 関連情報 == * [[Reactの基礎 - Hooksの基礎]] * [[Reactの基礎 - useState]] * [[Reactの基礎 - useContext]] * [[Reactの基礎 - カスタムHook]] <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板,React,JavaScript,TypeScript,TSX,useReducer,useState,useContext,Hooks,Reducer,Redux,状態管理,Immer,Discriminated Union,dispatch,グローバル状態 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Rust]][[カテゴリ:Web]]
Reactの基礎 - useReducer
に戻る。