原文: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来完成