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

useReducer-基礎(chǔ)概念篇

useReducer-使用篇

useReducer-配合useContext使用

我們?cè)诘谝黄恼轮薪榻B了JavaScript中的reducer以及他的一些特點(diǎn)缚甩,對(duì)reducer不熟悉的小伙伴可以先看看第一篇七芭。

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

useState版login

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

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

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

上面Demo我們定義了5個(gè)state來描述頁(yè)面的狀態(tài),在login函數(shù)中當(dāng)?shù)卿洺晒蚝妗⑹r(shí)進(jìn)行了一系列復(fù)雜的state設(shè)置苔严。可以想象隨著需求越來越復(fù)雜更多的state加入到頁(yè)面孤澎,更多的setState分散在各處届氢,很容易設(shè)置錯(cuò)誤或者遺漏,維護(hù)這樣的老代碼更是一個(gè)噩夢(mèng)覆旭。

useReducer版login

下面看看如何使用useReducer改造這段代碼退子,先簡(jiǎn)單介紹下useReducer。

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

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

第一個(gè)參數(shù):reducer函數(shù)姐扮,沒錯(cuò)就是我們上一篇文章介紹的絮供。第二個(gè)參數(shù):初始化的state。返回值為最新的state和dispatch函數(shù)(用來觸發(fā)reducer函數(shù)茶敏,計(jì)算對(duì)應(yīng)的state)。按照官方的說法:對(duì)于復(fù)雜的state操作邏輯缚俏,嵌套的state的對(duì)象惊搏,推薦使用useReducer贮乳。

聽起來比較抽象,我們先看一個(gè)簡(jiǎn)單的例子:

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

    // 第二個(gè)參數(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會(huì)根據(jù)dispatch的action恬惯,返回最終的state向拆,并觸發(fā)rerender
                Count: {state.count}
                // dispatch 用來接收一個(gè) 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 ( 
            //  返回頁(yè)面JSX Element
        )
    }

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

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

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

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

除此之外還有一個(gè)好處榆芦,我們?cè)谇拔奶徇^Reducer其實(shí)一個(gè)UI無關(guān)的純函數(shù),useReducer的方案是的我們更容易構(gòu)建自動(dòng)化測(cè)試用例喘鸟。

總結(jié)

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

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

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

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

其實(shí)我們并不是推薦所有的state放一起菊匿,而是如果確實(shí)有很多state跨多個(gè)組件公用需要放到page級(jí)別維護(hù)付呕,這時(shí)候可以考慮使用useReducer。

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

最后慣例,歡迎大家star我們的人人貸大前端團(tuán)隊(duì)博客疹蛉,所有的文章還會(huì)同步更新到知乎專欄掘金賬號(hào)活箕,我們每周都會(huì)分享幾篇高質(zhì)量的大前端技術(shù)文章。如果你喜歡這篇文章可款,希望能動(dòng)動(dòng)小手給個(gè)贊育韩。

參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市闺鲸,隨后出現(xiàn)的幾起案子筋讨,更是在濱河造成了極大的恐慌,老刑警劉巖摸恍,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悉罕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡立镶,警方通過查閱死者的電腦和手機(jī)壁袄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來媚媒,“玉大人嗜逻,你說我怎么就攤上這事$哉伲” “怎么了栈顷?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)嵌巷。 經(jīng)常有香客問我萄凤,道長(zhǎng),這世上最難降的妖魔是什么搪哪? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任靡努,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颤难。我一直安慰自己神年,他們只是感情好已维,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布行嗤。 她就那樣靜靜地躺著,像睡著了一般垛耳。 火紅的嫁衣襯著肌膚如雪栅屏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天堂鲜,我揣著相機(jī)與錄音栈雳,去河邊找鬼。 笑死缔莲,一個(gè)胖子當(dāng)著我的面吹牛哥纫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播痴奏,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛀骇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了读拆?” 一聲冷哼從身側(cè)響起擅憔,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎檐晕,沒想到半個(gè)月后暑诸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辟灰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年个榕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芥喇。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡西采,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乃坤,到底是詐尸還是另有隱情苛让,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布湿诊,位于F島的核電站狱杰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏厅须。R本人自食惡果不足惜仿畸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧错沽,春花似錦簿晓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至放可,卻和暖如春谒臼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耀里。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工蜈缤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冯挎。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓底哥,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親房官。 傳聞我的和親對(duì)象是個(gè)殘疾皇子趾徽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355