翻譯|How to Use the useReducer Hook

原文:How to Use the useReducer Hookjavascript

在全部的新React Hooks,或許僅僅是由於名字,就可能成爲使用最多的一個. "reducer"這個單詞會讓不少人聯想起Redux-可是讀本文,你沒必要事先理解Redux.html

咱們這裏要談的"reducer"實際問題是,如何利用useReducer的優勢來管理組件中的複雜狀態(state),新的hook對於Redux意味着什麼?Redux須要hook嗎?(對不起,有點跑題).java

[^譯註:結合Redux和useReducer來闡述問題,多是一個很好的出發點, Redux的reducer和useReducer核心都是根據組件dispatch的Action的type,payload來對State對象進行更新.概念是徹底同樣的,若是對Redux不是太瞭解, 能夠藉助useReducer來理解這個過程. 留給你大腦的轉變過程是,若是二者之間的這種相同點存在,能夠遷移嗎?]react

在本文中,咱們會探討一下useReducer.在組件中管理複雜state,要比useState的方式厲害的多.git

什麼是Reducer?

若是你熟悉Redux,或者數組的reduce方法,你就應該知道reducer 是什麼?.若是你不熟悉,"reducer"是一個奇特的單詞,表明一個函數接收兩個值,返回一個值.程序員

若是有一個數組, 你想把其中的元素組合成單個值,"函數式編程"的作法是使用數組的reduce函數. 例如,若是你有一個數組,元素是數字,你想獲得數字的綜合, 能夠編寫一個reducer函數,傳遞給數組的reduce方法,例如:github

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

若是以前沒看過這樣的用法,可能有點暈. 這裏所作的是針對數組的每一個元素調用函數,傳遞的參數是前一個total和當前的number.函數返回值成爲新的total,第二個傳遞給reduce的參數(在這裏是0)就是total的初始值. 在這個例子中,輸入的函數將會調用三次:npm

  • 用個(0,1)調用,返回1
  • 用個(1,2)調用,返回3
  • 用個(3,3)調用,返回1
  • reduce返回6,結果存儲在sum中.

可是,這和useReducer有什麼關係?

我花了半頁的篇幅倆解釋數組的reduce的緣由是由於,useReducer接受相同的參數,基礎的工做是相同的.你傳遞一個reducer函數和初始值(initial state). reducer接收當前的state和一個action,返回一個新的state.咱們能夠寫一個相似的合計reducer:編程

useReducer((state,action)=>{
 Return state+action;
},0)
複製代碼

那麼如何觸發這個操做? action是如何輸入函數的. 想到這個問題就對了.redux

[^譯註:這裏的這個問題絕對是學習Redux時,使人最困惑的地方]

useReducer返回有兩個元素的數組,相似useState hook. 第一個元素是當前的state,第二個參數是dispatch函數. 實際的代碼以下:

const [sum, dispatch] = useReducer((state, action) => {
  return state + action;
}, 0);
複製代碼

注意"state"能夠是任何值,不必定非要是一個對象. 能夠是數字,數組,任何東西.

接着來看一個使用reducer的完整組件實例:

import React, { useReducer } from 'react';

function Counter() {
  // 首次渲染會建立一個state,後續的渲染會保存結果.
  const [sum, dispatch] = useReducer((state, action) => {
    return state + action;
  }, 0);

  return (
    <> {sum} <button onClick={() => dispatch(1)}> Add 1 </button> </> ); } 複製代碼

能夠在CodeSandbox 試試

能夠看到,點擊按鈕,dispatch一個action,參數是1, 這個值會被加到當前的state上, 以後組件會用新的state(更大的值)來渲染組件.

我能夠的把"action"寫成這樣.沒有使用{type:"INCREMENT_BY",value:1}的形式或者其餘相似Redux的形式,由於reducer不必定必需要準守Redux的type模式.Hooks的世界是一個全新的世界:這一點很值得考慮,是否能發現舊有模式的價值,並保持它們,仍是使用新的模式.

稍微複雜一點的例子

如今來看一個和典型Redux reducer 很是接近的實例.咱們要建立一個組件管理購物車列表,同時也會使用另外一個hook:useRef

首先導入兩個hook:

import React,{useReducer,useRef} from 'react'
複製代碼

接着建立組件,設置ref和reducer.ref保留對錶單輸入的引用,便於咱們獲取表單的值(也能夠經過組件內部state,傳遞value,onChange props來獲取值,可是用useRef能夠很好的展示它的用法)

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的用法,以後在返回reducer話題.

useRefhook 可讓咱們建立一個DOM元素的持久化引用. 調用useRef會建立一個空的引用(能夠傳遞參數進行初始化).返回的對象有一個current屬性,因此在實例中,咱們能夠經過inputRef.current來訪問DOM元素的輸入值. 若是你對React.createRef()很熟悉,這裏的工做原理是相同的.

useRef返回的對象不只僅能夠承載一個DOM元素的引用,它能夠承載作組件內的任何特定值,而且在渲染中保持固定.耳熟! 必須的.

useRef也可用於建立泛型實例化變量,和React 類組件中的this.whatever=value作法同樣. 惟一的區別是要寫成"side effect"的形式,因此就不能在組件渲染過程當中改變它了-只能在useEffect內部執行. 官方Hook問答 有實例講解.

回到useReducer的例子

from包裝input,在按下Enter鍵時觸發提交函數. 如今須要編寫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',默認分支:其餘的任務.

當reduce獲取到"add" action 之後, 它會返回一個新的數組包含了舊的元素,在末尾添加新的一條項目.

咱們使用數組的長度做爲自增ID.在這個實例中用自增ID是能夠的,可是在實際的app中,不太理想,由於有可能致使重複的ID和bugs(最好是使用相似uuid的軟件包,或者由服務器生成一個惟一的ID!)

在用戶點擊Enter鍵時,會調用handleSubmit函數,因此須要調用preventDefault來避免正頁面的重載. 以後調用dispatch,參數是action.在app中,咱們想讓action更像Redux形式-擁有type屬性,附帶一些數據. 此外還有清除輸入.

這個階段的代碼CodeSandBox

移除一項

如今添加從列表中移除項目的能力

挨着項目添加 刪除按鈕,點擊時會dispatch一個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>
    </>
  );
}
複製代碼

這個階段的代碼CodeSandBox

練習:清除列表

在額外添加一個內容,清空列表的按鈕,做爲練習.

<ul>之上添加一個按鈕, 添加onClick屬性,能夠dispatch,type爲"clear"的action.以後在reducer中添加分支處理"clear"action.

那麼... Redux就此終結篇章了嗎?

不少人初次看到useReducer就想,React如今內置reducer了,還有Context能夠在全局範圍傳遞數據,因此Redux已死! 我想給出個人一些想法,由於我猜你也很想知道到Redux的命運將會如何?

[^譯註: 我我的觀點, useReducer的引入不只不會讓Redux很難堪,反而會讓程序員藉助useReducer對Redux有更深的認識,Redux的構架學習可能會有不少的回報,此刻若是捨棄React-Native,投入flutter的懷抱, flutter-Redux的就再也不是一個負擔了.]

我不認爲useReducer會殺死Redux,Context也不會. 我認爲這兩個方法只是擴展了React state管理的方法範圍而已,因此真正的狀況是他們會減小使用Redux的用例.

Redux仍然比Context+useReducer所作的工做多得多- Redux有Redux DevTools用於拍錯,能夠定製化的組件,還有全生態系統的助手軟件包.你能夠大膽的說,Redux在不少狀況下都有點殺雞用牛刀.可是我認爲它仍然是很是強有力的.

Redux提供的全局store可讓你集中控制app的data.useReducer是特定組件私有的.使用useReducer,useContext構建一個迷你版的Redux也是徹底可行的. 若是你想作,它們徹底能夠知足需求(Twitter上有不少人已經作了,有截圖).我我的仍然想念DevTools.

總之-Redux活蹦亂跳的.Hooks不會讓Redux過期.

本身嘗試一下

一下是幾個小的應用,能夠用useReducerhook來完成

  • 建一所房子,有一盞燈,按按鈕能夠調光-關,低亮度,中等亮度,最高亮度
  • 作一個鍵盤鎖,有6個按鈕, 正確的順序會解鎖. 真確的按鍵順序事先記錄在state中, 順序不正確會充值.
相關文章
相關標籤/搜索