[Note] React - Hooks: useReducer


Posted by urlun0404 on 2022-10-18

一般情況下,useState 其實已經很夠用,但是遇到以下情況時,可以考慮用 useReducer

  1. 管理多個有相關聯的 state;
  2. 一個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>
    )
}


這裡有幾點要注意:

  1. React會讓 emailReducer 函式自動接收最新的state和action,所以不必特地放在component裡面去接收state;
  2. 最好用 useEffect 才能確保 reducer states 更新後才接收到最新的 states,否則在其他地方使用 reducer states 都有可能接收到不是最新的states;
  3. Dependencies optimization:可以善用object destructuring把要取得的更新值(e.g., isValid)獨立解構出來,當作 useEffect 的 dependencies,避免整個state object (e.g., emailState)更新, useEffect 會一直更新;
    • Dependencies也可以用 [emailState.isValid, passwordState.isValid, others....]



參考資料
React - The Complete Guide (incl Hooks, React Router, Redux)


#frontend #React #hook #useReducer #note







Related Posts

[重新理解 C++] TMP(3): Type deduction 和一些運用

[重新理解 C++] TMP(3): Type deduction 和一些運用

【單元測試的藝術】Chap 11: 設計與可測試性

【單元測試的藝術】Chap 11: 設計與可測試性

LeetCode JS Easy 2704. To Be Or Not To Be

LeetCode JS Easy 2704. To Be Or Not To Be


Comments