這一次完全搞定useReducer-使用篇

useReducer-基礎概念篇html

useReducer-使用篇前端

useReducer-配合useContext使用react

咱們在第一篇文章中介紹了JavaScript中的reducer以及他的一些特色,對reducer不熟悉的小夥伴能夠先看看第一篇git

React Hook功能正式發佈以後,容許在function component中擁有state和反作用(useEffect)。官方提供了兩種state管理的hook:useState、useReducer。下面咱們會經過一系列Demo逐步說明如何使用useReducer管理state。github

useState版login

咱們先看看登陸頁常規的使用useState的實現方式:數組

function LoginPage() {
        const [name, setName] = useState(''); // 用戶名
        const [pwd, setPwd] = useState(''); // 密碼
        const [isLoading, setIsLoading] = useState(false); // 是否展現loading,發送請求中
        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標識
                    setError(error.message);
                    setName('');
                    setPwd('');
                    setIsLoading(false);
                });
        }
        return ( 
            // 返回頁面JSX Element
        )
    }
複製代碼

上面Demo咱們定義了5個state來描述頁面的狀態,在login函數中當登陸成功、失敗時進行了一系列複雜的state設置。能夠想象隨着需求愈來愈複雜更多的state加入到頁面,更多的setState分散在各處,很容易設置錯誤或者遺漏,維護這樣的老代碼更是一個噩夢。app

useReducer版login

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

const [state, dispatch] = useReducer(reducer, initState);
複製代碼

useReducer接收兩個參數:post

第一個參數:reducer函數,沒錯就是咱們上一篇文章介紹的。第二個參數:初始化的state。返回值爲最新的state和dispatch函數(用來觸發reducer函數,計算對應的state)。按照官方的說法:對於複雜的state操做邏輯,嵌套的state的對象,推薦使用useReducer。測試

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

// 官方 useReducer Demo
    // 第一個參數:應用的初始化
    const initialState = {count: 0};

    // 第二個參數:state的reducer處理函數
    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函數
        const [state, dispatch] = useReducer(reducer, initialState);
        return (
            <> // useReducer會根據dispatch的action,返回最終的state,並觸發rerender Count: {state.count} // dispatch 用來接收一個 action參數「reducer中的action」,用來觸發reducer函數,更新最新的狀態 <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函數如今更清晰的表達了用戶的意圖,開始登陸login、登陸success、登陸error。LoginPage不須要關心如何處理這幾種行爲,那是loginReducer須要關心的,表現和業務分離。

另外一個好處是全部的state處理都集中到了一塊兒,使得咱們對state的變化更有掌控力,同時也更容易複用state邏輯變化代碼,好比在其餘函數中也須要觸發登陸error狀態,只須要dispatch({ type: 'error' })

useReducer可讓咱們將whathow分開。好比點擊了登陸按鈕,咱們要作的就是發起登錄操做dispatch({ type: 'login' }),點擊退出按鈕就發起退出操做dispatch({ type: 'logout' }),全部和how相關的代碼都在reducer中維護,組件中只須要思考What,讓咱們的代碼能夠像用戶的行爲同樣,更加清晰。

除此以外還有一個好處,咱們在前文提過Reducer其實一個UI無關的純函數,useReducer的方案是的咱們更容易構建自動化測試用例。

總結

最後咱們總結一下這篇文章的一些主要內容:使用reducer的場景

  • 若是你的state是一個數組或者對象
  • 若是你的state變化很複雜,常常一個操做須要修改不少state
  • 若是你但願構建自動化測試用例來保證程序的穩定性
  • 若是你須要在深層子組件裏面去修改一些狀態(關於這點咱們下篇文章會詳細介紹)
  • 若是你用應用程序比較大,但願UI和業務可以分開維護

這篇文章咱們介紹了使用useReducer,幫助咱們集中式的處理複雜的state管理。但若是咱們的頁面很複雜,拆分紅了多層多個組件,咱們若是在子組件觸發這些state變化呢,好比在LoginButton觸發登陸操做? 咱們將在下篇文章介紹如何處理複雜組件樹結構的reducer共享問題。

若是有小夥伴看過thinking-in-react可能會有疑問,官方不是推薦State應該有子組件本身維護麼,爲何還要集中式的處理?

其實咱們並非推薦全部的state放一塊兒,而是若是確實有不少state跨多個組件公用須要放到page級別維護,這時候能夠考慮使用useReducer。

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

最後慣例,歡迎你們star咱們的人人貸大前端團隊博客,全部的文章還會同步更新到知乎專欄掘金帳號,咱們每週都會分享幾篇高質量的大前端技術文章。若是你喜歡這篇文章,但願能動動小手給個贊。

參考連接

相關文章
相關標籤/搜索