前言
接續前一篇,這篇是 useReducer,請直接開始閱讀文章!
文章架構
如上篇,我會按照官方文件的架構,首先講解定義,再用一些簡單的範例講解應該怎麼使用這些 Hooks,最後再講解一些容易犯錯的用例。
定義
當元件中具有相當複雜的邏輯,用 useState 會影響你的元件的可維護性,就可以使用 useReducer 來提升元件的可維護性。
const [state, dispatch] = useReducer(reducer, initialArg, init?)
我們可以看到他分成五個部分:
- 傳入
- reducer: 是一個函式,用來定義如何更新 state,有兩個參數分別是 state 和 action,reducer 會透過我們傳入的 action 來更新 state。
- initArg: 和 useState 中的 initState 一樣,用來設定一個或多個 state。
- init?: 這是用來產生預設 state 的函式,這個函式會被傳入 initArg,用來給 init 函式使用,這個函式回傳才會是最終的 initArg。(可能有點難懂,等等用範例解釋!)
- 傳出
- state: state 就是你儲存的值,一開始會和你的 initArg 是一樣的值,注意這裡和 useState 的 state 一樣都是唯讀的,要改變你的 state 就 dispatch 一個 action。
- dispatch: 用 action 來觸發 reducer 更新 state。
範例
以下是一些使用 useReducer 的範例。
// reducer function
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
name: state.name,
age: state.age + 1
};
}
case 'changed_name': {
return {
name: action.nextName,
age: state.age
};
}
}
throw Error('Unknown action: ' + action.type);
}
// function component
function Form() {
const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 });
function handleButtonClick() {
dispatch({ type: 'incremented_age' });
}
function handleInputChange(e) {
dispatch({
type: 'changed_name',
nextName: e.target.value
});
}
// ...
}
注意事項
1. initArg with init function
這裡講解 initArg 和 init function 的關係,以下範例提供參考。
// without init function
// function component
function Form() {
const [state, dispatch] = useReducer(reducer, [
{id: 0, name: 'John1'},
{id: 1, name: 'John2'},
{id: 2, name: 'John3'},
// ... and other 47 elements
]);
// ...
}
// with init function
// init function
function createInitState(name) {
let initList = []
for(let i = 0;i < 50;i++){
initList.push({
id: i,
name: name + i.toString(),
})
}
return initList;
}
// function component
function Form() {
const [state, dispatch] = useReducer(reducer, 'John', createInitState);
// init state will be a list with 50 elements
// ...
}
2. 注意 state 的更新時機
和 useState 一樣,在同一個函式中 dispatch 並不會馬上更新 state,會拿到一樣的結果(請參考 block 1),如果真的需要馬上得到更新後的結果,請參考 block 2。
// block 1
function handleClick() {
console.log(state.age); // 42
dispatch({ type: 'incremented_age' }); // Request a re-render with 43
console.log(state.age); // Still 42!
setTimeout(() => {
console.log(state.age); // Also 42!
}, 5000);
}
// block 2
function handleClickWithGuessNextState() {
const action = { type: 'incremented_age' };
dispatch(action);
const nextState = reducer(state, action);
console.log(state); // { age: 42 }
console.log(nextState); // { age: 43 }
}
3. 注意 reducer 中的回傳值
請注意 reducer 中的每個 return 都要回傳完整的 state,如果只有回傳部分的 state,那麼在該次的 reducer 執行完畢後,就會只剩下當次回傳的 state 內容。
// reducer function
// state: {
// name: 'John',
// age: 18
// }
//
// reducer with wrong return
// 1. dispatch({type: 'incremented_age'})
// state: {
// age: 19
// }
//
// 2. dispatch({type: 'changed_name', nextName: 'annie'})
// state: {
// name: 'annie'
// }
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
age: state.age + 1
// missing name
};
}
case 'changed_name': {
return {
// missing age
name: action.nextName,
};
}
}
throw Error('Unknown action: ' + action.type);
}
結語
一開始看稍稍有點看不懂,但官網一樣給了很多範例參考,又多掌握了一個 hook,希望趕快寫完剩下的開始嘗試運用這些 hooks 到我的專案裡!