這一次徹底搞定useReducer-使用篇

useReducer-基礎(chǔ)概念篇

useReducer-使用篇

useReducer-配合useContext使用

我們在第一篇文章中介紹了JavaScript中的reducer以及他的一些特點,對reducer不熟悉的小伙伴可以先看看第一篇。

React Hook功能正式發(fā)布之后,允許在function component中擁有state和副作用(useEffect)。官方提供了兩種state管理的hook:useState、useReducer。下面我們會通過一系列Demo逐步說明如何使用useReducer管理state。

useState版login

我們先看看登錄頁常規(guī)的使用useState的實現(xiàn)方式:

    function LoginPage() {
        const [name, setName] = useState(''); // 用戶名
        const [pwd, setPwd] = useState(''); // 密碼
        const [isLoading, setIsLoading] = useState(false); // 是否展示loading,發(fā)送請求中
        const [error, setError] = useState(''); // 錯誤信息
        const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登錄

        const login = (event) => {
            event.preventDefault();
            setError('');
            setIsLoading(true);
            login({ name, pwd })
                .then(() => {
                    setIsLoggedIn(true);
                    setIsLoading(false);
                })
                .catch((error) => {
                    // 登錄失敗: 顯示錯誤信息、清空輸入框用戶名、密碼、清除loading標(biāo)識
                    setError(error.message);
                    setName('');
                    setPwd('');
                    setIsLoading(false);
                });
        }
        return ( 
            //  返回頁面JSX Element
        )
    }

上面Demo我們定義了5個state來描述頁面的狀態(tài),在login函數(shù)中當(dāng)?shù)卿洺晒Α⑹r進行了一系列復(fù)雜的state設(shè)置??梢韵胂箅S著需求越來越復(fù)雜更多的state加入到頁面,更多的setState分散在各處,很容易設(shè)置錯誤或者遺漏,維護這樣的老代碼更是一個噩夢。

useReducer版login

下面看看如何使用useReducer改造這段代碼,先簡單介紹下useReducer。

    const [state, dispatch] = useReducer(reducer, initState);

useReducer接收兩個參數(shù):

第一個參數(shù):reducer函數(shù),沒錯就是我們上一篇文章介紹的。第二個參數(shù):初始化的state。返回值為最新的state和dispatch函數(shù)(用來觸發(fā)reducer函數(shù),計算對應(yīng)的state)。按照官方的說法:對于復(fù)雜的state操作邏輯,嵌套的state的對象,推薦使用useReducer。

聽起來比較抽象,我們先看一個簡單的例子:

    // 官方 useReducer Demo
    // 第一個參數(shù):應(yīng)用的初始化
    const initialState = {count: 0};

    // 第二個參數(shù):state的reducer處理函數(shù)
    function reducer(state, action) {
        switch (action.type) {
            case 'increment':
              return {count: state.count + 1};
            case 'decrement':
               return {count: state.count - 1};
            default:
                throw new Error();
        }
    }

    function Counter() {
        // 返回值:最新的state和dispatch函數(shù)
        const [state, dispatch] = useReducer(reducer, initialState);
        return (
            <>
                // useReducer會根據(jù)dispatch的action,返回最終的state,并觸發(fā)rerender
                Count: {state.count}
                // dispatch 用來接收一個 action參數(shù)「reducer中的action」,用來觸發(fā)reducer函數(shù),更新最新的狀態(tài)
                <button onClick={() => dispatch({type: 'increment'})}>+</button>
                <button onClick={() => dispatch({type: 'decrement'})}>-</button>
            </>
        );
    }

了解了useReducer基本使用方法后,看看如何使用useReducer改造上面的login demo:

    const initState = {
        name: '',
        pwd: '',
        isLoading: false,
        error: '',
        isLoggedIn: false,
    }
    function loginReducer(state, action) {
        switch(action.type) {
            case 'login':
                return {
                    ...state,
                    isLoading: true,
                    error: '',
                }
            case 'success':
                return {
                    ...state,
                    isLoggedIn: true,
                    isLoading: false,
                }
            case 'error':
                return {
                    ...state,
                    error: action.payload.error,
                    name: '',
                    pwd: '',
                    isLoading: false,
                }
            default: 
                return state;
        }
    }
    function LoginPage() {
        const [state, dispatch] = useReducer(loginReducer, initState);
        const { name, pwd, isLoading, error, isLoggedIn } = state;
        const login = (event) => {
            event.preventDefault();
            dispatch({ type: 'login' });
            login({ name, pwd })
                .then(() => {
                    dispatch({ type: 'success' });
                })
                .catch((error) => {
                    dispatch({
                        type: 'error'
                        payload: { error: error.message }
                    });
                });
        }
        return ( 
            //  返回頁面JSX Element
        )
    }

乍一看useReducer改造后的代碼反而更長了,但很明顯第二版有更好的可讀性,我們也能更清晰的了解state的變化邏輯。

可以看到login函數(shù)現(xiàn)在更清晰的表達了用戶的意圖,開始登錄login、登錄success、登錄error。LoginPage不需要關(guān)心如何處理這幾種行為,那是loginReducer需要關(guān)心的,表現(xiàn)和業(yè)務(wù)分離。

另一個好處是所有的state處理都集中到了一起,使得我們對state的變化更有掌控力,同時也更容易復(fù)用state邏輯變化代碼,比如在其他函數(shù)中也需要觸發(fā)登錄error狀態(tài),只需要dispatch({ type: 'error' })。

useReducer可以讓我們將whathow分開。比如點擊了登錄按鈕,我們要做的就是發(fā)起登陸操作dispatch({ type: 'login' }),點擊退出按鈕就發(fā)起退出操作dispatch({ type: 'logout' }),所有和how相關(guān)的代碼都在reducer中維護,組件中只需要思考What,讓我們的代碼可以像用戶的行為一樣,更加清晰。

除此之外還有一個好處,我們在前文提過Reducer其實一個UI無關(guān)的純函數(shù),useReducer的方案是的我們更容易構(gòu)建自動化測試用例。

總結(jié)

最后我們總結(jié)一下這篇文章的一些主要內(nèi)容:使用reducer的場景

  • 如果你的state是一個數(shù)組或者對象
  • 如果你的state變化很復(fù)雜,經(jīng)常一個操作需要修改很多state
  • 如果你希望構(gòu)建自動化測試用例來保證程序的穩(wěn)定性
  • 如果你需要在深層子組件里面去修改一些狀態(tài)(關(guān)于這點我們下篇文章會詳細(xì)介紹)
  • 如果你用應(yīng)用程序比較大,希望UI和業(yè)務(wù)能夠分開維護

這篇文章我們介紹了使用useReducer,幫助我們集中式的處理復(fù)雜的state管理。但如果我們的頁面很復(fù)雜,拆分成了多層多個組件,我們?nèi)绻谧咏M件觸發(fā)這些state變化呢,比如在LoginButton觸發(fā)登錄操作? 我們將在下篇文章介紹如何處理復(fù)雜組件樹結(jié)構(gòu)的reducer共享問題。

如果有小伙伴看過thinking-in-react可能會有疑問,官方不是推薦State應(yīng)該有子組件自己維護么,為什么還要集中式的處理?

其實我們并不是推薦所有的state放一起,而是如果確實有很多state跨多個組件公用需要放到page級別維護,這時候可以考慮使用useReducer。

PS:推薦兩篇React State管理的文章

最后慣例,歡迎大家star我們的人人貸大前端團隊博客,所有的文章還會同步更新到知乎專欄掘金賬號,我們每周都會分享幾篇高質(zhì)量的大前端技術(shù)文章。如果你喜歡這篇文章,希望能動動小手給個贊。

參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容