useReducer基础用法
React Hook useReducer基础用法
# useReducer概念解释
第四个Hook(钩子函数)是useReducer,他的作用是"钩住"某些自定义数据对应的dispatch所引发的数据更改事件。useReducer可以替代useState。实现更为复杂逻辑的数据修改。
在React16.8版本以前,通常需要使用第三方Redux来管理React的公共数据,但自从 React Hook 概念出来以后,可以使用 useContext + useReducer 轻松实现 Redux 相似功能。
# useReducer是来解决什么问题的?
useReducer是useState的升级版(实际上应该是原始版),可以实现复杂逻辑修改,而不是像useState那样只是直接赋值修改的。
补充说明:
- 在React源码中,实际上useState就是由useReducer实现的,所以useReducer准确来说是useState的原始版。
- 无论哪一个Hook函数,本质上都是通过事件驱动来实现视图层更新的。
# useReducer函数源码
首先看一下React源码中的ReactHooks.js (opens new window)。
export function useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
2
3
4
5
6
7
8
还是那句话看不懂也没关系,这并不是Hook源码分析,而是如何使用Hook,之所以贴出源码只是重点要看useReducer函数的第3个参数,一般的话是传2个参数就好了,但是我上次在github看到有个人的项目传了3个参数,可以这么理解,第3个参数其实只是第1个和第2个参数的某种转化。可以完全忽略这个问题,每次传值只需要传2个参数就好了。
# useReducer基本用法
useReducer(reducer, initiaValue)函数通常传入2个参数,第1个参数为我们定义的一个"由dispatch引发的数据修改处理函数",第2个参数为自定义数据的默认值,useReducer函数会返回自定义变量的引用和该自定义变量对应的"dispatch"。
当看到了dispatch,想到了原生JS中的EvenetEmitter,事实上React Hook做了底层的事件驱动处理,拿到的dispatch以及"事件处理函数"reducer,都是被React Hook 封装过后的,并不是真正的抛出和事件处理函数。
但是我接下来说这个useReducer依旧还会使用到"事件抛出、事件处理函数"等文字。
其实这个useReducer的用法很简单的,了解了事件驱动,使用过EventEmitter、或者使用过Redux,就很好理解了。
# 代码形式
import React, { useReducer } from 'react'; // 引入useReducer
// 定义好"事件处理函数" reducer
function reducer(state, action) {
switch (action) {
case 'xx':
return xxxx;
case 'xx':
return xxxx;
default:
return xxxx;
}
}
function Component() {
// 声明一个变量xxx,以及对应修改xxx的dispatch
// 将事件处理函数reducer的默认值initialVal作为参数传递给useReducer
const [xxx, dispatch] = useReducer(reducer, initialValue);
// 若想获取xxx的值,直接使用xxx即可
// 若想修改xxx的值,通过dispatch来修改
dispatch('xx');
}
// 注意:上述代码中的action只是最基础的字符串形式,事实上action可以是多属性的object,这样可以自定义更多属性和更多参数值
// 例如 action 可以是 {type: xx, param: xxx}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 拆解说明
- 具体已经在示例代码中做了多项注释,不再重复。(不想浪费时间)
'reducer'补充说明
- reducer英文单词本身意思是"减速器、还原剂",但是我一直把reducer称呼为"事件处理函数",但事实上reducer确实扮演了一个事件处理函数。
- 千万别把useReducer中的reducer和原生中的Array.prototype.reduce()搞混,他们两个只是刚好都是用了reduce单词而已,两者本身没有任何内在关联。(上海的南京路与南京的上海路)
'xxx'补充说明
假设定义的变量名为xxx,那么只能通过dispatch来修改xxx,不要尝试通过 xxx = newValue 这种形式直接修改变量的值,React 不允许这样做。
'dispatch'补充说明
再次强调一下,dispatch并不是真正的Event.dispatch,但是完全可以把它当成Event.dispatch来理解,只不过useReducer中的的dispatch(xxx)函数抛出内容并不是event,而是一个包含修改信息的对象,该对象不仅可以是字符串,还可以是复杂对象。
'initalValue'补充说明
initalValue是自定义变量的默认值,该值可以是简单类型(number、string),也可以是复杂类型(object、array)。
个人建议供参考:即使该值是简单类型,也建议单独定义出来而不是直接将值写在useReducer函数中,因为单独定义可以更加清晰的读懂数据结构,尤其是initalValue为复杂类型的时候。
# useReducer使用示例1
举例:某React组建内部有一个变量count,有3个button,点击之后分别可以修改count的值。3个按钮具体的功能为:第1个button点击之后count+1,第2个button店址之后count-1,第3个button点击之后count x 2 (翻倍)。
如果使用useState来实现,那肯定是没问题的,每个button点击之后分别运算得到相对应的新值,然后将该值直接通过setCount赋值给count。
使用useReducer来实现相同功能,代码如下:
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action) {
case 'add':
return state + 1;
case 'sub':
return state - 1;
case 'mul':
return state * 2;
default:
console.log('默认');
return state;
}
}
function CountComponent() {
const [count, dispatch] = useReducer(reducer, 0)
return <div>
{count}
<button onClick={() => { dispatch('add') }}>add</button>
<button onClick={() => { dispatch('sub') }}>sub</button>
<button onClick={() => { dispatch('mul') }}>mul</button>
</div>
}
export default CountComponent;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 代码分析
- 3个按钮点击之后,不再具体去修改count的值,而是采用dispatch('xxx')的形式"抛出修改count的事件",事件处理函数reducer"捕获到修改count的事件后",根据该事件携带的命令类型来进一步判断,并且真正执行对count的修改。 之前我说过,我只是以事件驱动的语言来描述整个过程,因为能够更加理解。
- 3个按钮只是负责通知reducer"我要做的事情",具体怎么做完全由reducer来执行,这样实现了修改数据具体执行逻辑与按钮点击处理函数的抽离。
如果不使用useReducer,而是使用useState,那么对count的每一种修改逻辑代码、都必须分散写在每个按钮的点击事件处理函数中。
# useReducer使用示例2
若只是修改count的功能,那么useReducer的优势还没有全部体现出来,来继续写。
举例:在上面的例子中对count执行的修改,数值变动都是固定的,即+1、-1、*2。那么来实现一下点击按钮之后,能够自主控制增加多少、减多少、或者乘以多少。
很简单,将dispatch('xxx')中的xxx由字符串改为obj,obj可以携带更多属性作为参数传给reducer。比如之前对"加"的命令dispatch('add'),修改为dispatch({type: 'add', param: 2)。reducer可以通过action.type来区分是哪种命令、通过action.param来获取对应的参数。
为了简化代码,点击按钮后,随机产生一个数字,并将该数字作为param的值,传递给reducer。
修改后的代码:
import React, { useReducer } from 'react';
function reducer(state, action) {
// 根据action.type来判断该执行哪种操作
switch (action.type) {
// count 最终加多少,取决于 action.param的值
case 'add':
return state + action.param;
case 'sub':
return state - action.param;
case 'mul':
return state * action.param;
default:
console.log('默认');
return state;
}
}
function getRandom() {
return Math.floor(Math.random()* 10);
}
function CountComponent() {
const [count, dispatch] = useReducer(reducer);
return <div>
{count}
<button onClick={() => { dispatch({type: 'add',param: getRandom()})}}>add</button>
<button onClick={() => { dispatch({type: 'sub',param: getRandom()})}}>add</button>
<button onClick={() => { dispatch({type: 'mul',param: getRandom()})}}>add</button>
</div>
}
export default CountComponent;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
同样的道理,把示例中的count由简单类型改为复杂类型,来存储更多的变量。但是,建议不要把useReducer对应的变量设计的多余复杂。
使用了useReducer,可以使用比较复杂的逻辑和参数对内部变量进行修改。
但是有一点,示例1和示例2中所有的变量都是在同一个组件内定义和修改的,现实项目中肯定牵扯到不同模块组建之间共享并且修改某个变量,具体的,下次再写吧。🤪🤪🤪