深入理解 React Reducer 與應用
2025/04/14
本章節將引導工程師了解 React 中的 Reducer 概念及其應用。學習如何使用 useReducer 進行狀態管理,打造更具結構性的應用程式,並討論何時使用 Reducer 能提升專案的維護性與效率。

理解 Redux 與初探 Reducer
在 React 生態系中,Redux 是一個廣泛使用的狀態管理工具,而 reducer 則是 Redux 的核心之一。Reducer 是一個純函數,接收當前的 state 與 action,並回傳新的 state。它是不可變的,不會直接修改 state,而是返回經過計算的新 state。這樣的特性使狀態管理更加可控且易於測試。例子如下:
const initialState = { count: 0 }; function counterReducer(state = initialState, action) { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } }
在此例中,我們定義了一個簡單的 counterReducer,它管理 count
的狀態,並根據不同的 action 類型更新 state。
useReducer 與 useState 比較
React 提供了 useState 與 useReducer 兩個 Hook 來進行狀態管理。useState 方便用於簡單的狀態變更,而 useReducer 適合處理複雜的狀態邏輯或多組狀態變量。使用 useReducer 時,可以更清晰地定義各種 action 以及對應的狀態更新邏輯。
例如,我們可以用 useReducer 來管理 complex state:
const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> Count: {state.count} <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> </div> ); }
useReducer 的基本結構與使用
useReducer 是一個 Hook,用於處理複雜的 state 邏輯。其基本結構類似於 Redux,主要包含:state、dispatch、reducer function。useReducer 接受兩個參數:reducer function 和初始 state,返回一個包含當前狀態與 dispatch 方法的陣列。
範例如下:
const initialState = { count: 0 }; const [state, dispatch] = useReducer(reducer, initialState);
dispatch 用來發送 action,reducer function 接收當前 state 和 action,返回新的 state。此模式讓我們能更清楚地管理狀態轉變邏輯,有助於減少錯誤和提升穩定性。
實作用例:從 useState 遷移到 useReducer
假設我們有一個相對簡單的計數器範例,最初使用useState 管理狀態:
function Counter() { const [count, setCount] = useState(0); return ( <div> Count: {count} <button onClick={() => setCount(count + 1)}>+</button> <button onClick={() => setCount(count - 1)}>-</button> </div> ); }
隨著業務需求變得複雜,const 開始變得不夠用。我們轉向 useReducer,以便更有效地管理狀態。
const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> Count: {state.count} <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> </div> ); }
通過使用 useReducer,我們將狀態更新函數集中到 reducer function 中,這有助於減少錯誤源,增強代碼的可讀性和易維護性。
何時使用 useReducer 更適合
useReducer 適用於需要複雜狀態邏輯的情況,例如多個 state 互相關聯或需要執行多步更新。
在下列情況中,使用 useReducer 會比 useState 更有優勢:
- 狀態邏輯複雜並涉及多個層次的更新。
- 新的 state 依賴於舊的 state。
- 需要將業務邏輯分離到 Reducer 函數中。
- 跨越多個子組件共享狀態邏輯。
此外,使用 useReducer 我們可以輕鬆組合多個狀態變更,並利用 Redux middleware 進行更精細的控制。這對於大型應用專案尤其有益。
如何設計良好的 Reducer
設計良好的 Reducer 是提升應用程式維護性和效能的關鍵。以下是一些設計良好 Reducer 的重要建議:
- 保持 Reducer 的純粹性: 確保 Reducer 函數沒有副作用,它只是單純返回一個新的 state。
- 細化 Reducer 函數: 將不同邏輯分散在多個 Reducer,使用 combineReducers 函數進行合併。
- 預設情況處理: 為預期之外的 action 設置預設行為,通常是回傳現有的 state。
- 避免冗長的 switch 語句: 當可能的時候,將 action 類型分組,減少 switch 的語句分支。
- 使用 TypeScript: 這有助於為 Reducer 建立強型別的約束,減少潛在的錯誤。
例如,透過 TypeScript,我們可以表達更明確的型別:
type Action = { type: 'increment' } | { type: 'decrement' }; 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; } }
使用 useReducer 創建複合狀態管理
在大型 React 應用中,管理許多相似的 state 是常見的需求。useReducer 可以幫助我們創建複合狀態,輕鬆管理多種狀態邏輯。
一個常見的應用例子是表單數據管理:
const initialState = { username: '', email: '', password: '' }; function formReducer(state, action) { switch (action.type) { case 'setField': return { ...state, [action.field]: action.value }; case 'reset': return initialState; default: return state; } } function SignupForm() { const [state, dispatch] = useReducer(formReducer, initialState); return ( <form> <input value={state.username} onChange={e => dispatch({ type: 'setField', field: 'username', value: e.target.value })} /> <input value={state.email} onChange={e => dispatch({ type: 'setField', field: 'email', value: e.target.value })} /> <input type="password" value={state.password} onChange={e => dispatch({ type: 'setField', field: 'password', value: e.target.value })} /> <button type="submit">Sign Up</button> </form> ); }
此範例展示了如何使用 useReducer 管理多個表單欄位,提供了一個強大的方法以避免重複代碼並提升可擴展性。
最佳實踐:結合 immer 進行不可變性處理
為確保 state 的不可變性,useReducer 視為 Redux,其需通過手動實作物件展開運算符來更新 state。這在處理深層次結構時可能變得容易出錯和繁瑣。在這種情景下,immer 是一個能夠輔助的工具,提供清晰且簡潔的操作:
import produce from 'immer'; function reducer(state, action) { return produce(state, draft => { switch (action.type) { case 'increment': draft.count += 1; break; case 'decrement': draft.count -= 1; break; default: break; } }); }
透過 immer,我們能使用可變樣式的方式來描述不可變的更新,這使得 reducer 變得更簡潔易懂。不過,我們仍需謹慎對待,尤其是在處理大型物件時,避免無意間更改了其他部位內容。
用 Context API 與 useReducer 管理全域狀態
在大型應用中,全域狀態管理是個挑戰,尤其在多個組件間需要共享狀態時。結合 Context API 和 useReducer 是一個強大的解決方案。
我們可以透過以下方式設計全域狀態管理:
const GlobalStateContext = createContext(); const GlobalDispatchContext = createContext(); function App() { const [state, dispatch] = useReducer(reducer, initialState); return ( <GlobalStateContext.Provider value={state}> <GlobalDispatchContext.Provider value={dispatch}> <YourComponent /> </GlobalDispatchContext.Provider> </GlobalStateContext.Provider> ); }
使用上,我們可以透過 useContext
分別獲取 state 及 dispatch:
function YourComponent() { const state = useContext(GlobalStateContext); const dispatch = useContext(GlobalDispatchContext); return ( <div> <button onClick={() => dispatch({ type: 'increment' })}>+</button> </div> ); }
上述結構化的方式讓我們的應用程式能夠在任何地方訪問和更新狀態,這是一個強大且靈活的設計模式。
結論:運用 useReducer 提升應用維護
useReducer 是 React 中一個強大的狀態管理方法,尤其適合用於具有複雜業務邏輯的應用程式。通過本文,我們深入探討了 useReducer 的基礎用法及進階應用,希望提供給工程師們更好的思路。在整體的開發流程中,選擇適當的工具對應特定需求總是最佳策略。
工程師們可以考慮用 useReducer 來管理狀態,尤其是在維護複雜度較高的應用程式時。配合好設計的 reducer 與 Context API,甚至能取代一部分 Redux 的功能。雖然 useReducer 在某些情況下替代不了 Redux 的強大能力,但在小到中型的應用中絕對是個值得考慮的選擇。