useReducer
在要管理的 state 變多之後,因為還會配上一個 setter,
這些 setter 通常又會有一些特定的邏輯要同時執行,就會包成一個函式,
整個元件可能就會開始膨脹而難以管理,這時候就可以考慮使用 useReducer。
用法
狀態定義
假設原本有 data、isLoading 這兩個 state:
const [data, setData] = useState([]);const [isLoading, setIsLoading] = useState(true);改為用 reducer 的方式管理後,這些 state 可以拉出來做成一個物件並設定初始值,
變數習慣命名會有 initial 前綴:
const initialStates = { data: [], isLoading: true,};原本用來改變狀態的 setter,會拉出來存到 reducer 這個變數,
來定義這些方法是給哪個狀態用的, 也建議改成用 switch 來分流要觸發的行為:
function reducer(state, action) { switch (action.type) { case 'SET_LOADING': return { ...state, isLoading: action.payload };
case 'SET_DATA': return { ...state, data: action.payload };
case 'RESET': return initialStates;
default: return state; }}每個 case 跟原本的 setter 看起來差不多,
差別在於想要變更的值要透過 action.payload 取得,
也可以額外定義新的 action 來一次改變好幾個 state,
像是讓全部 state 都回到初始值的 reset 功能。
在元件中執行
狀態跟方法都被分別定義好之後,這時就可以呼叫 useReducer,
把剛剛定義好的 state 與 reducer 傳入:
function MyComponent() { const [state, dispatch] = useReducer(reducer, initialStates);
console.log(state); // 會印出 initialStates 裡面的東西
return ( <div> {state.data.map((item) => ( <div>{item.name}</div> ))} </div> );}原本要使用 setter 觸發的狀態變更,要改用 dispatch 傳入對應格式,
type 就是剛剛在 reducer 裡面定好的 case,payload 就是傳入的值:
dispatch({ type: 'SET_LOADING', payload: false });coding style
改變狀態的方式從 setter 改為 dispatch 後,
這樣的格式 { type: 'SET_LOADING', payload: false } 讓語法看起來變冗長了,
如果有要把方法傳遞下去時,最好再封裝成一個函式,
原本傳遞下去的 props 命名,也建議改為更語意化的 on 事件,
否則掛在 JSX 上的 callback 會很長也不好閱讀:
function handleChange() { dispatch({ type: 'SET_LOADING', payload: false });}
return <ChildComponent onChange={handleChange} />;action type 目前全部都是用字串傳下去的,
如果字串有打錯的話第一時間找不到報錯,就很難抓到,
所以這些字串通常會拉出來做成可存取的物件:
const ACTION = { SET_LOADING: 'SET_LOADING', SET_DATA: 'SET_DATA',};
// 改成變數後能降低出包率dispatch({ type: ACTION.SET_LOADING, payload: false,});也推薦在 reducer 的定義中加上錯誤提示:
const reducer = (state, action) => { switch (action.type) { case 'SET_LOADING': return { ...state, isLoading: action.payload };
case 'SET_DATA': return { ...state, data: action.payload };
case 'RESET': return initialCompStates;
default: { throw Error('Unknown action: ' + action.type); } }};