在 React 設定 state,會透過 useState 的第二個 setState Function 做處理,但這樣做會造成以下的問題。
舉例來看以下這個 state 結構
const [obj, setObj] = useState({
a: '1',
b: {
c: '2'
},
});
想修改 obj 中 a 或 c 的值,直覺上來看,會寫出以下的寫法
<button onClick={() => setObj({a: '01'})} /> Set a</button>
// 上面這樣的寫法是不對的
// 預期結果是 {a: '01', b: {c: '2'}}
// 但會變成是 {a: '01'} ❌
<button onClick={() => setObj({b: {c: '02'}})} /> Set c</button>
// 上面這樣的寫法是不對的
// 預期結果是 {a: '1', b: {c: '02'}}
// 但會變成是 {b: {c: '02'}} ❌
正確的寫法應為
<button onClick={() => setObj({...obj, a: '01'})} /> Set a</button>
<button onClick={() => setObj({...obj,b: {c: '02'}})} /> Set c</button>
當 state 物件越複雜,操作的方法越多,那 setState Function 的處理邏輯也就更難清楚的做管控。
Facebook 的開發者為了解決複雜的資料狀態結構,而提出了 Flux 結構。在 Flux 觀念下,操作流程和資料的過程會變成如下:
Action
可以使用Dispatch
,派發指定的規則 Action 和需要的參數 Payload 到 Store
,Store 就是做集中做狀態管理的地方。State
,View 因為 State 變動而重新渲染。State
的 Function,就是 Reducer
Function。後面介紹的常用的狀態管理工具 - Redux
就是採用 Flux 結構。
Store
會是集中管理專案各種所需的 State
Data 及 Reducer
Function 的地方,通常我們會獨立成一個模組,讓元件在需要時引入。
但現在要先介紹一個 React 提供用來實現簡易 Flux 結構的 Hook - useReducer
。當狀態還只有在單一元件中的多樣化邏輯操作時,不需要為其特別建立 Store,只需要定義 Reducer Function 就可以了。
當我們發現操作 state 變得較為複雜,使用 useState 的 setState Function 做對應的狀態邏輯操作會散落在不同地方時,就可將 state 的改變統一放在 Reducer Function 去做管理。
如果元件的狀態簡單,用
useState
即可。若狀態變得複雜,就可以轉用useReducer
- 將操作狀態的邏輯從元件中的不同操作 Function 移到一個單獨的 Reducer Function 中去,使元件更易於管理。
const [state, dispatch] = useReducer(reducerFn, initStateValue);
// 如果程式不需要重置 state,使用上述語法即可
or
const [state, dispatch] = useReducer(reducerFn, initStateValue, initStateFn);
// initStateFn 讓你可以將初始化 state 的邏輯提取到 reducer 外。
// 而且也方便了將來處理重置 state 的 action
useState
製作的 Counter 元件因為 state 是比較複雜的物件,所以可以發現在 setState Function 的處理上,是多樣化的操作方法。
const Counter = () => {
// 初始化 state
const initialState = { status: false, count: 0 };
const [state, setState] = React.useState(initialState);
return (
<div>
<div>Count: {state.count}</div>
<div>Status: {String(state.status)}</div>
<button onClick={()=>{
setState({...state, count: state.count + 1})}
}>Increment</button>
<button onClick={()=>{
setState({...state, count: state.count - 1})}
}>Decrement</button>
<button onClick={()=>{
setState({...state, status: !state.status})}
}>Change Status</button>
<button onClick={()=>{
setState(initialState)}
}>Reset</button>
</div>
);
};
操作結果:https://codepen.io/lala-lee-jobs/pen/RwygRMa?editors=0011
useReducer
製作的 Counter 元件const Counter = () => {
// 初始化 state
const initialState = { status: false, count: 0 };
// 定義 reducer function,集中管控 state 及 action 對應的邏輯處理
// 依據 action type 對應不同的邏輯處理及回傳要變動的 state 結果
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return { ...state, count: state.count + 1 };
case "decrement":
return { ...state, count: state.count - 1 };
case "reset":
return {...initialState};
case "change-status":
return { ...state, status: !state.status };
default:
return state;
}
}
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<div>
<div>Count: {state.count}</div>
<div>Status: {String(state.status)}</div>
<button onClick={
() => dispatch({ type: "increment" })
}>Increment</button>
<button onClick={
() => dispatch({ type: "decrement" })
}>Decrement</button>
<button onClick={
() => dispatch({ type: "change-status" })
}>Change Status</button>
<button onClick={
() => dispatch({ type: "reset" })
}>Reset</button>
</div>
);
};
操作結果:https://codepen.io/lala-lee-jobs/pen/rNvwMgL?editors=0011
到目前為此,都是透過 props 讓父元件傳遞資料給子元件,但元件階層如果跨越了很多層級時,就會需要不斷的做 props 轉傳,接下來要介紹的 useContext Hook 就可以做到在多層元件間的資料管理與傳遞。
https://hackmd.io/@Heidi-Liu/redux
https://chentsulin.github.io/redux/index.html
https://blog.csdn.net/ftell/article/details/121411371