原文:How to Use the useReducer Hook
javascript
在全部的新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
若是你熟悉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
1
1
,2)調用,返回33
,3)調用,返回1reduce
返回6,結果存儲在sum
中.我花了半頁的篇幅倆解釋數組的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
的用法,以後在返回reducer話題.
useRef
hook 可讓咱們建立一個DOM元素的持久化引用. 調用useRef
會建立一個空的引用(能夠傳遞參數進行初始化).返回的對象有一個current
屬性,因此在實例中,咱們能夠經過inputRef.current
來訪問DOM元素的輸入值. 若是你對React.createRef()
很熟悉,這裏的工做原理是相同的.
從useRef
返回的對象不只僅能夠承載一個DOM元素的引用,它能夠承載作組件內的任何特定值,而且在渲染中保持固定.耳熟! 必須的.
useRef
也可用於建立泛型實例化變量,和React 類組件中的this.whatever=value
作法同樣. 惟一的區別是要寫成"side effect"的形式,因此就不能在組件渲染過程當中改變它了-只能在useEffect
內部執行. 官方Hook問答
有實例講解.
用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.
不少人初次看到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過期.
一下是幾個小的應用,能夠用useReducer
hook來完成