前導
在 React 中,狀態管理是構建複雜應用的重要一環,其中 useContext 與 Redux 都提供了解決方案,但它們各有優缺點和適用場景。
useContext
更加簡單、直觀,適合較小或中等規模的應用。Redux
則提供了更嚴謹的架構,適合大型應用或需要細緻控制狀態更新的場景。- 小型/中型應用: 如果應用不需要處理大量複雜狀態,且狀態更新頻率較低,使用 useContext(有時候與 useReducer 結合)可以減少開發複雜度。
- 大型/複雜應用: 如果應用狀態較多且更新頻繁,或需要跨多個組件共享大量數據,Redux 提供的集中管理和調試能力能夠更好地應對複雜情況。
核心原則
- 單一數據來源
整個應用的狀態存儲在一個唯一的 store 中,這使得應用狀態集中、易於管理與調試。 - 狀態只讀
改變狀態的唯一途徑是發送(dispatch)一個 action。這些 action 是普通 JavaScript 物件,用於描述“發生了什麼事”。這樣的設計強制應用遵循單向數據流,使狀態變化更容易追蹤。 - 使用純函數處理狀態變更
Reducer 是純函數,它根據先前的狀態和收到的 action 計算並返回新的狀態。純函數不會改變傳入的狀態,而是返回一個全新的狀態物件,從而保持狀態的不變性。
2. 主要概念個別解析
- Store
Redux 的 store 是一個對象,包含了應用的所有狀態。唯一的 store 提供了一個全局的狀態樹,便於跨組件共享數據。 - Action
Action 是一個描述事件的 JavaScript 物件,必須包含一個 type 屬性(通常為字符串),這個屬性定義了該 action 的類型。Action 可以攜帶其他數據(payload),幫助 reducer 更新狀態。 - Reducer
Reducer 是一個純函數,負責根據當前狀態和 action 的類型來生成新的狀態。它不會修改原始狀態,而是返回一個新的狀態物件,這樣做能保持狀態的不可變性。 - Dispatch
當需要改變狀態時,應用會通過 dispatch 發送一個 action,store 收到 action 後會調用相應的 reducer 來更新狀態。 - Subscribe
應用可以訂閱 store 的狀態變化,當狀態發生改變時,訂閱者(通常是 UI 組件)會收到通知並更新其視圖。
Redux Toolkit 是什麼?
Redux Toolkit 是 Redux 官方推薦的工具集,旨在簡化 Redux 的使用,減少樣板程式碼,並提供更好的開發體驗。它包含了一些常用的 Redux 功能,並提供了一些工具來幫助你更快地建立 Redux store、定義 reducer 和 action。
Redux Toolkit 使用範例
這裡是一個使用 Redux Toolkit 建立的計數器小程式,具備 +1
、-1
和 addBy(amount)
三種功能。
步驟概覽
- 建立 Redux Store:使用
@reduxjs/toolkit
建立 store,並撰寫 reducer 來處理計數邏輯。 - 建立 React 元件:使用
react-redux
連接 store 並透過按鈕操作數值。 - 組合與渲染應用。
實際操作
- 首先,我們可以按照先前我們創建 React 環境一樣創建新的工作環境。
- 然號需要安裝必要的套件:
npm install @reduxjs/toolkit react-redux
- 建立 Redux Store,先在工作目錄的
src
資料夾下新增store
資料夾,接著在裡面新增store.jsx
。
在store.jsx
中使用configureStore
來建立 Redux Toolkit store:
import { configureStore, createSlice } from '@reduxjs/toolkit';
// 建立 slice (類似 reducer)
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 }, // 初始狀態
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
addBy: (state, action) => {
state.value += action.payload;
},
},
});
// 匯出 actions
export const { increment, decrement, addBy } = counterSlice.actions;
// 建立 store
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
export default store;
在 createSlice
裡,name
主要是:
- 用來區分 slice,這樣 Redux DevTools 會顯示它的名稱,方便除錯。
- 與 action type 相關,RTK 會自動幫我們生成 action type,例如:這些 action type 就是
name + reducer function 名稱
的組合。
counter/increment
counter/decrement
counter/addBy
- 程式碼中,
name: 'counter'
並不是一定要叫 "counter",你可以改成 "myCounter" 或 其他名稱,但它影響的是 action type 的前綴。
在 createSlice
裡,我們定義 initialState
:
initialState: { value: 0 }
- 這代表這個 slice 一開始的狀態是 { value: 0 }。
在 reducer 裡,state
代表當前的 slice (目前counter)的狀態(例如 value: 5)
increment: (state) => {
state.value += 1;
}
- 每次
increment
執行時,這個state.value
就會+1
。 - 例如
state.value += 1;
讓value
變成6
。
在 Redux Toolkit 裡,當我們呼叫 action
時,可以傳遞參數,這些參數就會存在 action.payload
。
addBy: (state, action) => {
state.value += action.payload;
}
- 這裡
action.payload
代表的是我們dispatch
時傳入的值。
在 Counter.js
內部:
dispatch(addBy(5));
這個 dispatch(addBy(5))
實際上:
{
type: 'counter/addBy',
payload: 5
}
- 這個物件會傳遞到
addBy
的reducer
,讓action.payload
變成 5,然後 state.value += 5。
最後當reducer
更新完 state.value
後,Redux 會:
- 更新 Store(新的
state.value
變成 6)。 - 通知所有訂閱該
state
的元件(React 會重新渲染 UI)。
- 建立計數器元件
第四步驟,我們在 Counter.js
中撰寫 React 元件:
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, addBy } from './store/store.jsx';
const Counter = () => {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
const [inputValue, setInputValue] = useState(0);
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>Counter: {count}</h1>
<button onClick={() => dispatch(increment())}>+1</button>
<button onClick={() => dispatch(decrement())}>-1</button>
<br /><br />
<input
type="number"
value={inputValue}
onChange={(e) => setInputValue(Number(e.target.value))}
/>
<button onClick={() => dispatch(addBy(inputValue))}>Add By</button>
</div>
);
};
export default Counter;
因為我們在 Counter.js
中用 useSelector()
取得 state.value
:
const count = useSelector((state) => state.counter.value);
當 Redux Store 更新 state.value
後,React 會偵測到 count
變動,並重新渲染 <h1>
:
<h1>Counter: {count}</h1>
例如: 畫面上的數字會從 5 → 6,使用者能看到變化!
補充一下:
當你使用 useSelector()
取得 state
時:
const count = useSelector((state) => state.counter.value);
state
代表整個 Redux Storestate.counter
代表counterSlice
state.counter.value
取得counterSlice
內的value
今天假設你把 name: "counter"
改成 name: "myCounter"
、initialState: { count: 0 }
改成initialState: { count: 0 }
,則useSelector()
也要改成:
const count = useSelector((state) => state.myCounter.count);
- 最後設定 Provider 並渲染應用
第五步驟,在 App.js
中:
import React from 'react';
import { Provider } from 'react-redux';
import store from './store/store.jsx';
import Counter from './Counter.jsx';
const App = () => {
return (
<Provider store={store}>
<Counter />
</Provider>
);
};
export default App;
這樣我們就完成了React-Redux最簡單的應用。建議讀者可以實際操作一次並了解流程。