【譯】瞭解 useReducer

原文: Getting to Know the useReducer React Hook
做者:Kingsley Silas
譯者:博軒

useReducer 是 React 16.8.0 中爲數很少由官方提供的 React Hook 之一。它接受一個 reducer 函數 ,以及一個初始的應用程序狀態,而後返回當前應用程序的狀態,和一個調度函數(dispatch)。css

一個簡單的例子:html

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

這樣有什麼好處?一個好主意是讓咱們試着想象一下,一個應用初次加載屬性時的全部狀況。它多是可交互式的地圖上的一個起點。或許是容許用戶使用一個默認的模型來自定義選項,構建一個自定義汽車。這裏有一個很是簡潔的計算器,當計算器重置時,使用 useReducer 來使應用程序恢復默認狀態。react

https://codepen.io/dpgian/emb...redux

咱們將在這篇文章中深刻研究幾個例子,瞭解一下 useReducer Hook 自己,以及應該什麼時候使用。數組

全能的 reducer

提及 useState ,就不得不說起 JavaScript 的 reduce 方法。最開始,咱們很難將它們聯繫起來,可是 Sarah 的一篇關於 reducer 的文章 能夠幫助咱們更好的理解。函數

關於 reducer 最重要的一點就是: 它每次只返回一個值reducer 的工做就是減小。那個值能夠是數字,字符串,對象,數組或者對象,可是它老是一個值。 reducer 在不少狀況下都頗有效,可是他對於處理輸入一組值,返回一個值的狀況很是有用。

假設咱們有一個數字數組,reduce 將依次累加每個值。這是數組:學習

const numbers = [1, 2, 3]

...以及一個函數,每次 reducer 中的計算都會在控制檯打印出來。這有助於咱們理解 reducer 將數組提取爲單個數字的過程。this

const reducer = function (tally, number) { 
    console.log(`Tally: ${tally}, Next number: ${number}, New Total: ${tally + number}`)
    return tally + number
}

如今,讓咱們運行一個 reducer 。正如咱們所看到的,reduce 接收一個調度函數,以及一個初始狀態。讓咱們傳入一個 reducer 函數,以及一個初始值:0。spa

const total = numbers.reduce(reducer, 0)

這是控制檯打印的內容:翻譯

"Tally: 0, Next number: 1, New Total: 1"
"Tally: 1, Next number: 2, New Total: 3"
"Tally: 3, Next number: 3, New Total: 6"

reduce 是如何將一個初始值累加,獲得咱們的最終結果的。在這個例子中,最終結果是 6。

我也十分喜歡 Dave Ceddia 的示例 ,他展現瞭如何使用 reduce 來拼寫一個單詞:

var letters = ['r', 'e', 'd', 'u', 'c', 'e'];

// `reduce` takes 2 arguments:
//   - a function to do the reducing (you might say, a "reducer")
//   - an initial value for accumulatedResult
var word = letters.reduce(
    function(accumulatedResult, arrayItem) {
        return accumulatedResult + arrayItem;
    },
''); // <-- notice this empty string argument: it's the initial value

console.log(word) // => "reduce"

組合使用 useReducer ,state ,action

好的,接下來到了這篇文章的重點: useReducer 。到了這裏的一切都很重要,由於使用 reduce 調用一個函數來處理初始值的方式,就是咱們接下來的目標。它是同一種概念,可是會返回一個數組包含兩個元素,當前的狀態調度函數

const [state, dispatch] = useReducer(reducer, initialArg, init);
第三個參數 init 是什麼?它是一個可選值,能夠用來惰性提供初始狀態。這意味着咱們可使用使用一個 init 函數來計算初始狀態/值,而不是顯式的提供值。若是初始值可能會不同,這會很方便,最後會用計算的值來代替初始值。

爲了使它工做,咱們須要作一些事情:

  • 定義初始狀態
  • 提供一個包含 action 的函數來更新 state
  • 觸發 useReducer ,基於初始值計算並更新 state

計數器就是一個經典的例子。事實上這也是官方文檔使用這個例子的緣由:

https://codepen.io/kinsomicro...

這是一個很好的例子,由於它演示了每次經過單擊增長或減小按鈕觸發操做時如何使用初始狀態(零值)來計算新值。咱們甚至能夠在其中輸入一個「重置」按鈕,將總數恢復到初始狀態零。

示例:汽車定製器

https://codepen.io/geoffgraha...

在此示例中,咱們假設用戶已經選擇了本身要購買的汽車。可是,咱們但願用戶能夠爲汽車添加額外的選項。每一個選項的價格都會影響汽車的總價。

首先,咱們須要建立初始狀態,其中包括汽車,能夠跟蹤功能的空數組 features,$26,395 的起始價格 price,一個存放未選配件的列表 store ,用戶能夠選擇他們想要的東西。

const initialState = {
  additionalPrice: 0,
  car: {
    price: 26395,
    name: "2019 Ford Mustang",
    image: "https://cdn.motor1.com/images/mgl/0AN2V/s1/2019-ford-mustang-bullitt.jpg",
    features: []
  },
  store: [
    { id: 1, name: "V-6 engine", price: 1500 },
    { id: 2, name: "Racing detail package", price: 1500 },
    { id: 3, name: "Premium sound system", price: 500 },
    { id: 4, name: "Rear spoiler", price: 250 }
  ]
};

咱們的 reducer 功能將處理兩件事:添加和刪除新項目。

const reducer = (state, action) => {
  switch (action.type) {
    case "REMOVE_ITEM":
      return {
        ...state,
        additionalPrice: state.additionalPrice - action.item.price,
        car: { ...state.car, features: state.car.features.filter((x) => x.id !== action.item.id)},
        store: [...state.store, action.item]
      };
    case "BUY_ITEM":
      return {
        ...state,
        additionalPrice: state.additionalPrice + action.item.price,
        car: { ...state.car, features: [...state.car.features, action.item] },
        store: state.store.filter((x) => x.id !== action.item.id)
      }
    default:
      return state;
  }
}

當用戶選擇他想要的項目時,咱們更新汽車的 features,增長 additionalPrice 並從商店中刪除該項目。咱們確保 state 其餘部分會保持原樣。當用戶從功能列表中刪除項目時,咱們會執行相似操做 - 減小額外價格,將項目返回到商店。
如下是App組件的代碼。

const App = () => {
  const inputRef = useRef();
  const [state, dispatch] = useReducer(reducer, initialState);
  
  const removeFeature = (item) => {
    dispatch({ type: 'REMOVE_ITEM', item });
  }
  
  const buyItem = (item) => {
    dispatch({ type: 'BUY_ITEM', item })
  }
  
  return (
    <div>
      <div className="box">
        <figure className="image is-128x128">
          <img src={state.car.image} />
        </figure>
        <h2>{state.car.name}</h2>
        <p>Amount: ${state.car.price}</p>
        <div className="content">
          <h6>Extra items you bought:</h6>
          {state.car.features.length ? 
            (
              <ol type="1">
                {state.car.features.map((item) => (
                  <li key={item.id}>
                    <button
                      onClick={() => removeFeature(item)}
                      className="button">X
                    </button>
                    {item.name}
                  </li>
                ))}
              </ol>
            ) : <p>You can purchase items from the store.</p>
          }
        </div>
      </div>
      <div className="box">
        <div className="content">
          <h4>Store:</h4>
          {state.store.length ? 
            (
            <ol type="1">
              {state.store.map((item) => (
                <li key={item.id}>\
                  <button
                    onClick={() => buyItem(item)}
                    className="button">Buy
                  </button>
                  {item.name}
                </li>
              ))}
            </ol>
            ) : <p>No features</p>
          }
        </div>

        <div className="content">
        <h4>
          Total Amount: ${state.car.price + state.additionalPrice}
        </h4>
      </div>
      </div>
    </div>
  );
}

調度操做會包含所選項的詳細信息。咱們使用 action 的類型來肯定 reducer 函數如何處理狀態的更新。您能夠看到渲染視圖會根據您的操做而作出改變 - 從商店購買的商品會從商店中刪除,並添加到功能列表當中。此外,總金額也會更新。毫無疑問,咱們能夠對示例進行修改達到學習的目的。

咱們可使用 useState 來代替嗎?

聰明的讀者可能一直在想這個問題。個人意思是,setState 大體會作相同的事情,不是嗎?返回一個具有狀態的值,以及一個可使用新值從新渲染組件的函數。

const [state, setState] = useState(initialState);

咱們甚至可使用 useState 來實現官方文檔中的計數器的例子。可是 useReducer 在處理複雜狀態的時候是最優解。Kent C. Dodds 寫了一個二者之間的差別(雖然他常用 setState)他提供了一個 useReducer 的最佳實踐:

當你一個元素中的狀態,依賴另外一個元素中的狀態,最好使用 useReducer

例如,你正在完成一個井字遊戲。你的組件中的 狀態被稱爲 squares ,它包含了左右方格,以及其中的值。

個人經驗是使用 useReducer 來處理複雜的狀態,尤爲是初始狀態是基於其餘元素生成的狀況下。

useReducer Example

等等,咱們已經有 Redux 了!

若是你使用 Redux 工做,也會理解這裏所涉及的全部內容,由於它的設計理念是經過 Context API 來存儲,傳遞組件之間的狀態 - 沒必要經過其餘組件傳遞 props 來實現。

那麼, useReducer 取代 Redux 了嗎?不,個人意思是,你基本能夠經過 useContext hook 來實現你本身的 Redux,可是,這並不表明 Redux 沒有用了,它仍然有許多其餘的功能和優勢值得考慮。

你會在哪裏使用 useReducer ?他是否有比 setState 更好的地方?也許你能夠嘗試使用咱們這裏介紹的想法來構建一些東西,下面是一些想法。

  • 一個日曆,會展現今天的日期,但容許用戶選擇其餘日期。還能夠添加一個「今天」按鈕,幫助用戶返回到今天的日期。
  • 您能夠嘗試改進汽車示例 - 讓用戶擁有一個購物車列表。你能夠爲它定義初始狀態,而後用戶能夠添加他們想要的額外功能,並收取必定費用。這些功能能夠是預約義的,也能夠由用戶自定義。
本文已經聯繫原文做者,並受權翻譯,轉載請保留原文連接
相關文章
相關標籤/搜索