一般情況下,useState
其實已經很夠用,但是遇到以下情況時,可以考慮用 useReducer
:
- 管理多個有相關聯的 state;
- 一個state需要根據另一個state最新的狀態來更新(畢竟React的state更新並非是立即的)。
舉例來說,一個表單的每個input標籤都要確認input目前輸入值的狀態(valueState)和輸入值的正確性狀態(isValidState),因為這兩種states都與某個input相關,而且正確性狀態須根據輸入值狀態來更新,因此這種時候就很適合改用 useReducer
代替多個 useState
。
語法
const [state, disPatchFunc] = useReducer(reducerFunc, initState, initFunc);
- state:state,跟
useState
的 state 一樣 - dispatchFunc:dispatch an action to update state (NOT update value of new state)
- reducerFunc:Reducer函式,只要
dispatchFunc
dispatch新的action,React會自動觸發這個reducer函式去取得最新的state,並且根據dispatched action更新state,簡單來說這個函式有兩個作用:(1) 取得目前最新的state;(2) 根據dispatched action更新state。 - initState:初始狀態
- initFunc:用來建立初始狀態的函式,可以用這個函式賦值比較複雜的初始狀態,例如要透過HTTP requests取得data當作初始狀態。
範例
為程式碼簡潔,範例以email input當作示範,但表單事實上包含其他inputs(e.g., password input):
import {useState, useReducer} from 'react';
// It's triggered via dispatched action
function emailReducer(latestState, action){
// Update both value and isValid if receives action named USER_INPUT
if(action.type === 'USER_INPUT'){
return {value: action.val, isValid: action.val.includes('@')}
}
if(action.type === 'INPUT_BLUR'){
return {value: latestState.val, isValid: latestState.includes('@')}
}
// Default value
return {value: '', isValid: false};
}
export default function Form(){
const [formIsValid, setFormIsValid] = useState(false);
const [emailState, dispatchEmail] = useReducer(emailReducer, { value: '', isValid: null});
const changeEmail = (event) => {
dispatchEmail({
type: 'USER_INPUT' // action identifier
, val: event.target.value // action payload
})
}
const validateEmail = () => {
dispatchEmail({ type: 'INPUT_BLUR' });
}
// Destructure object to only get isValid
const {isValid: emailIsValid} = emailState;
const {isValid: passwordIsValid} = passwordState;
// Ensure getting the latest input to validate form
useEffect(()=>{
setFormIsValid(emailState.isValid && ...);
// }, [emailState, otherState...])
}, [emailIsValid, passwordIsValid, others....])
return(
<form>
<label html>e-mail</label>
<input
type="text"
id="email"
value={emailState.value}
onChange={changeEmail}
onBlur={validateEmail}
/>
</form>
)
}
這裡有幾點要注意:
- React會讓
emailReducer
函式自動接收最新的state和action,所以不必特地放在component裡面去接收state; - 最好用
useEffect
才能確保 reducer states 更新後才接收到最新的 states,否則在其他地方使用 reducer states 都有可能接收到不是最新的states; - Dependencies optimization:可以善用object destructuring把要取得的更新值(e.g., isValid)獨立解構出來,當作
useEffect
的 dependencies,避免整個state object (e.g., emailState)更新,useEffect
會一直更新;- Dependencies也可以用
[emailState.isValid, passwordState.isValid, others....]
- Dependencies也可以用
參考資料
React - The Complete Guide (incl Hooks, React Router, Redux)