如何使用useReducer Hook

爲了保證的可讀性,本文采用意譯而非直譯。前端

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!react

這個禮拜《大遷世界》有抽獎活動,獎品:專欄 《左耳聽風》 x3, 技術書 x5,歡迎關注回覆:抽獎

看到「reducer」這個詞,容易讓人聯想到Redux,可是在本文中,沒必要先理解Redux才能閱讀這篇文章。我們將一塊兒討論「reducer」其實是什麼,以及如何利用useReducer來管理組件中的複雜狀態,以及這個新鉤子對Redux意味着什麼?git

Reducer 是什麼鬼

若是你熟悉Redux或數組上中的reduce方法,你大概就知道「reducer」是什麼。 若是不熟悉,「reducer」大概是一個帶有2個值並返回1個值的函數這麼個意思。github

若是你有一系列的東西,而且想將這些東西組合成一個單獨的物體。「函數式編程」中就是使用Arrayreduce函數。 例如,若是你有一個數字數組而且想獲得數組中全部數字的總和,我們就能夠寫一個reducer函數並將它傳遞給reduce,以下所示:編程

let numbers = [1, 2, 3];
let sum = numbers.reduce((total, number) => {
    return total + number;
},0)

若是你之前沒用過這個,它可能看起來有點神祕。它所作的是爲數組的每一個元素調用函數,傳入前一個total和當前元素 number。不管你返回什麼,都會成爲新的totalreduce的第二個參數(在本例中爲0)是total的初始值。在本例中,reduce函數會被調用3次:redux

  • 調用 (0, 1) 返回 1
  • 調用 (1, 2) 返回 3
  • 調用 (3, 4) 返回 6

reduce返回6,它保存在sum中。數組

使用useReducer又會是什麼樣的?服務器

各位花了在半篇幅來解釋Arrayreduce函數,由於 useReducer 參數與 reduce 相同,而且工做方式基本同樣。 useReducer接收 (state, action) => newState,而且返回了一個與當前state成對的dispatch的方法。 我們使用 useReducer 來編寫上面的求和例子。函數式編程

useReducer((state, acton) => {
  return state + action
}, 0)

useReducer返回一個包含2個元素的數組,相似於useState hook。 第一個是當前狀態,第二個是dispatch方法,以下所示:函數

const [sum, dispatch] = useReducer((state, action) => {
  return state + action
}, 0)

注意state能夠是任何值,它不必定是一個對象,能夠是一個數字,一個數組,或者其餘任何類型。

儘管 useReducer 是擴展的 hook, 而 useState 是基本的 hook,但 useState 實際上執行的也是一個 useReducer。這意味着 useReducer 是更原生的,你能在任何使用 useState 的地方都替換成使用 useReducer

import React, { useReducer } from 'react';

function Counter() {
  // First render will create the state, and it will
  // persist through future renders
  const [sum, dispatch] = useReducer((state, action) => {
    return state + action;
  }, 0);

  return (
    <>
      {sum}

      <button onClick={() => dispatch(1)}>
        Add 1
      </button>
    </>
  );
}

點擊按鈕dispatch一個值爲1action,該action將被添加到當前狀態,而後組件使用新的狀態從新渲染。

這裏故意展現了,派發action沒有遵循Redux的典型模式{type: "INCREMENT BY"、value: 1}或其餘相似的東西。hook 的世界是一個新的世界:值得考慮的是,你是否發現舊的模式有價值並但願保留它們,或者你是否願意更改它們。

一些更復雜的例子

再來看看更接近典型Redux reducer 的例子。建立一個組件來管理購物列表,這裏看還會使用另一個 hook:useRef

首先,導入兩個 hook

import React, { useReducer, useRef } from 'react';

而後建立一個設置refreducer的組件。 ref保存對錶單的引用,以便我們能夠提取其值。

function ShoppingList() {
  const inputRef = useRef();
  const [items, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      // do something with the action
    }
  }, []);

  return (
    <>
      <form onSubmit={handleSubmit}>
        <input ref={inputRef} />
      </form>
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            {item.name}
          </li>
        ))}
      </ul>
    </>
  );
}

請注意,在這種狀況下,我們的「state」是一個數組。 我們經過useReducer第二個參數將它初始化爲一個空數組,並從reducer函數返回一個數組。

useRef Hook

useRef hook爲DOM節點建立持久引用。 調用useRef會建立一個空的節點。它返回的對象具備current屬性,所以在上面的示例中,我們可使用inputRef.current訪問輸入的DOM節點。 若是你熟悉React.createRef(),則其工做原理很是類似。

可是,useRef返回的對象不只僅是一種保存DOM引用的方法。 它能夠保存特定於此組件實例的任何值,而且它在渲染之間保持不變。

useRef可用於建立通用實例變量,就像使用具備this.whatever = valueReact類組件同樣。 惟一的問題是,寫入它會被視爲「反作用」,所以我們沒法在渲染過程當中更改它,須要在useEffect hook 中才能修改。

回到useReducer示例

咱們用表單來處理用戶的輸入,按回車提交表彰。 如今來編寫handleSubmit函數,該函數主要作的是將一個項添加到列表中,以及處理reducer中的 action

function ShoppingList() {
  const inputRef = useRef();
  const [items, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case 'add':
        return [
          ...state,
          {
            id: state.length,
            name: action.name
          }
        ];
      default:
        return state;
    }
  }, []);

  function handleSubmit(e) {
    e.preventDefault();
    dispatch({
      type: 'add',
      name: inputRef.current.value
    });
    inputRef.current.value = '';
  }

  return (
    // ... same ...
  );
}

reducer函數中主要判斷兩種狀況:一種用於action.type==='add'的狀況,還有就是默認下的狀況。

action.typeadd 時,它返回一個包含全部舊元素的新數組,以及最後的新元素。

這裏有一點須要注意的是,我們使用數組的length做爲一種自動遞增的 ID 方便演示,可是對於一個真正的應用程序來講這是不可靠,由於它可能致使重複的ID和bug。(最好使用uuid這樣的庫,或者讓服務器生成一個唯一的ID!)

當用戶在輸入框中按Enter鍵時會調用handleSubmit函數,所以我們須要調用preventDefault以免在發生這種狀況時從新加載整頁。 而後dispatch派發一個 action

刪除項

如今來看看如何從列表中刪除項的。

在項目中添加一個刪除<button>,點擊該按鈕派發 它將發送一個 action type === "remove"的操做,以及要刪除的項的索引。

而後我們只須要在reducer中處理該action

function ShoppingList() {
  const inputRef = useRef();
  const [items, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case 'add':
        // ... same as before ...
      case 'remove':
        // keep every item except the one we want to remove
        return state.filter((_, index) => index != action.index);
      default:
        return state;
    }
  }, []);

  function handleSubmit(e) { /*...*/ }

  return (
    <>
      <form onSubmit={handleSubmit}>
        <input ref={inputRef} />
      </form>
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            {item.name}
            <button
              onClick={() => dispatch({ type: 'remove', index })}
            >
              X
            </button>
          </li>
        ))}
      </ul>
    </>
  );
}

練習:清空列表

試着添加一個功能:添加一個清空列表的按鈕。

<ul>上方插入一個按鈕,併爲其提供一個onClick prop,派發一個action ,type 爲「clear」的動做,並在 reducer 方法執行清空列表的動做。

能夠在前面 CodeSandbox的基礎上完成。

Redux 會死嗎

大部分人看到useReducer hook, React 如今已經內置了reducer ,它有Context傳遞數據,因此可能會想到 Redux 會不會所以就死了,如下是原做者給出的一些見解。

做者不認爲useReducer會殺死Redux。React Hook 擴展了React在狀態管理方面的能力,因此會讓使用 Redux的狀況減小。

Redux仍然比Context + useReducer的組合作得更多,它具備Redux DevTools 用於調試,可定製中間件、,以及整個相關庫生態系統,固然 Redu x在不少地方都被過分使用了,但它仍然具備強大的功能。

Redux提供了一個全局存儲,能夠在其中集中保存應用程序數據。useReducer本地化到特定組件。可是,沒有什麼能夠阻止我們使用useReduceruseContext構建本身的迷你redux 。若是你想這麼作,並且符合你的須要,那就去作吧!

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

交流

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq44924588...

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

相關文章
相關標籤/搜索