Jeff的隨手筆記

學習當一個前端工程師

0%

『Day 21』理解 React Reducer

前言

回想我之前做購物車 side project 的時候,在處理購物車的商品資料傳遞時,我是使用 Context(上下文)來處理這個狀況。雖然當時可以完成需求,但總覺得程式碼非常雜亂,寫起來有種說不出來的彆扭感。當時可能因為是練習作品,就沒有太在意這個問題。

直到後來工作時接觸到了 Redux,才恍然大悟,原來當初那種不舒服的感覺是來自於狀態管理的混亂。其實在處理較複雜的狀態邏輯時,像是購物車這種需要大量狀態更新的功能,React 引入了 useReducer,讓開發者可以使用與 Redux 相似的狀態管理模式,但只在元件範圍內操作,而非全域應用範圍。

今天就讓我分享一下 Reducer 的使用心得,看看它是如何幫助我們更優雅地處理複雜的狀態邏輯。

為什麼需要 Reducer?

想像一下,當你的元件需要處理多個相關的狀態,而且每個狀態的更新邏輯都分散在不同的地方,這時候程式碼就會變得很難追蹤和維護。以下幾種情況特別適合使用 Reducer:

  1. 元件中有許多分散的狀態更新邏輯
  2. 狀態之間有複雜的相依關係
  3. 需要集中管理相關的狀態邏輯

Reducer 的核心概念

使用 Reducer 最大的轉變是思考方式的改變。不再是直接告訴 React「要做什麼」,而是描述「使用者做了什麼」。這種方式讓程式碼更容易理解和維護。

實作 Reducer 的三個步驟

  1. 定義 Reducer 函式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function reducer(state, action) {
switch (action.type) {
case 'increment': {
return { count: state.count + 1 };
}
case 'decrement': {
return { count: state.count - 1 };
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}

  1. 在元件中使用 useReducer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useReducer } from 'react';

function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });

return (
<>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
);
}

  1. 設計合適的動作(Action)
    每個動作都應該清楚地描述使用者的操作意圖,例如:
1
2
3
4
5
6
// 好的動作命名
dispatch({ type: 'add_todo', text: '寫部落格' });

// 避免過於技術性的命名
dispatch({ type: 'set_todo_array' });

Reducer 的重要特性

1. 純函式的特性

Reducer 必須是純函式,這意味著:

  • 相同的輸入必須產生相同的輸出
  • 不能有副作用(例如修改外部變數或發送 API 請求)
  • 不能修改傳入的參數

2. 動作設計原則

  • 每個動作都應該描述單一且完整的使用者互動
  • 即使一個操作會造成多個資料的變更,也使用單一動作來表達
  • 動作的名稱應該清楚描述「發生了什麼事」,而不是「要做什麼事」

總結

使用 Reducer 不只是換個方式寫程式,更是一種思維的轉變。透過集中管理狀態更新邏輯,我們可以讓程式碼更容易理解和維護。雖然一開始可能需要寫比較多的程式碼,但這些「投資」絕對值得,特別是在處理複雜的狀態邏輯時。