推荐使用并手写实现redux-actions原理
@
一、前言
为什么介绍redux-actions呢?
第一次见到主要是接手公司原有的项目,发现有之前的大佬在处理redux的时候引入了它。
发现也确实 使得 在对redux的处理上方便了许多,而我为了更好地使用一个组件或者插件,都会去去尝试阅读源码并写成文章 ,这个也不例外。
发现也确实有意思,推荐大家使用redux的时候也引入redux-actions
在这里就介绍一下其使用方式,并且自己手写实现一个简单的redux-actions
二、介绍
在学习 redux 中,总觉得 action 和 reducer 的代码过于呆板,比如
2.1 创建action
let increment = ()=>({type:"increment"})
2.2 reducer
let reducer = (state,action)=>{
switch(action.type){
case "increment":return {count:state.count+1};break;
case "decrement":return {count:state.count-1};break;
default:return state;
}
}
2.3 触发action
dispatch(increment())
综上所示,我们难免会觉得 increment 和 reducer 做一个小 demo 还行,遇到逻辑偏复杂的项目后,项目管理维护就呈现弊端了。所以最后的方式就是将它们独立出来,同时在 reducer 中给与开发者更多的主动权,不能仅停留在数字的增增减减。
redux-actions主要函数有createAction、createActions、handleAction、handleActions、combineActions。
基本上就是只有用到createAction,handleActions,handleAction
所以这里我们就只讨论这三个个。
三、 认识与手写createAction()
3.1 用法
一般创建Action方式:
let increment = ()=>({type:"increment"})
let incrementObj = increment();// { type:"increment"}
使用createAction 创建 action
import { createAction } from 'redux-actions';
const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}
let objincrement = increment(10);// {type:"increment",paylaod:10}
我们可以看到
let increment = ()=>({type:"increment"})
let incrementObj = increment();// { type:"increment"}
与
const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}
是等效的,那为什么不直接用传统方式呢?
不难发现有两点:
- 传统方式,需要自己写个函数来返回incrementObj,而利用封装好的createAtion就不用自己写函数
- 传统方式,在返回的incrementObj若是有payload需要自己添加上去,这是多么麻烦的事情啊,你看下面的代码,如此的不方便。但是用了createAction返回的increment,我们添加上payload,十分简单,直接传个参数,它就直接把它作为payload的值了。
let increment = ()=>({type:"increment",payload:123})
3.2 原理实现
我们先实现个简单,值传入 type参数的,也就是实现下面这段代码的功能
const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}
我们发现createAction('increment')()才返回最终的action对象。这不就是个柯里化函数吗?
所以我们可以非常简单的写出来,如下面代码所示,我们把type类型当作action对象的一个属性了
function createAction(type) {
return () => {
const action = {
type
};
return action;
};
}
好了现在,现在实现下面这个功能,也就是有payload的情况
const increment = createAction('increment');
let objincrement = increment(10);// {type:"increment",paylaod:10}
很明显,这个payload是 在createAction('increment')返回的函数的参数,所以我们轻而易举地给action添加上了payload。
function createAction(type) {
return (payload) => {
const action = {
type,
payload
};
return action;
};
}
但是像第一种情况我们是不传payload的,也就是说返回的action是不希望带有payload的,但是这里我们写成这样就是 默认一定要传入payload的了。
所以我们需要添加个判断,当不传payload的时候,action就不添加payload属性。
function createAction(type) {
return (payload) => {
const action = {
type,
};
if(payload !== undefined){
action.payload = payload
}
return action;
};
}
在实际项目中我更喜欢下面这种写法,但它是等价于上面这种写法的
function createAction(type) {
return (payload) => {
const action = {
type,
...payload?{payload}:{}
};
return action;
};
}
其实createAction的参数除了type,还可以传入一个回调函数,这个函数表示对payload的处理。
const increment = createAction('increment');
let objincrement = increment(10);// {type:"increment",paylaod:10}
像上面的代码所示,我们希望的是传入10之后是返回的action中的payload是我们传入的2倍数
const increment = createAction('increment',(t)=> t * 2);
let objincrement = increment(10);// {type:"increment",paylaod:20}
现在,就让我们实现一下。
function createAction(type,payloadCreator) {
return (payload) => {
const action = {
type,
};
if(payload !== undefined){
action.payload = payloadCreator(payload)
}
return action;
};
}
卧槽,太简单了吧! 但是我们又犯了前边同样的错误,就是我们使用createAction的时候,不一定会传入payloadCreator这个回调函数,所以我们还需要判断下
function createAction(type,payloadCreator) {
return (payload) => {
const action = {
type,
};
if(payload !== undefined){
action.payload = payloadCreator?payloadCreator(payload):payload
}
return action;
};
}
卧槽,完美。
接下來看看 redux-action的 handleActions吧
四、认识handleActions
我们先看看传统的reducer是怎么使用的
let reducer = (state,action)=>{
switch(action.type){
case "increment":return {count:state.count+1};break;
case "decrement":return {count:state.count-1};break;
default:return state;
}
}
再看看使用了handleActions
const INCREMENT = "increment"
const DECREMENT = "decrement"
var reducer = handleActions({
[INCREMENT]: (state, action) => ({
counter: state.counter + action.payload
}),
[DECREMENT]: (state, action) => ({
counter: state.counter - action.payload
})
},initstate)
这里大家不要被{[DECREMENT]:(){}} 的写法吓住哈,就是把属性写成变量了而已。
我们在控制台 console.log(reducer) 看下结果

最后返回的就是一个 reducer 函数。
这样就实现了 reducer 中功能化的自由,想写什么程序,我们只要写在
{[increment]:(state,action)=>{}}
这个函数内就行,同时也可以把这些函数独立成一个文件,再引入进来就行
import {increment,decrement}from "./reducers.js"
var initstate = {count:0}
var reducer = createReducer({
[INCREMENT]: increment,
[DECREMENT]: decrement
},initstate)
reducers.js
//reducers.js
export let increment = (state,action)=>({counter: state.counter + action.payload})
export let decrement = (state,action)=>({counter: state.counter - action.payload})
可见,
handleactions 可以简化 reducers 的写法 不用那么多 switch 而且可以把函数独立出来,这样reducer就再也不会有一大堆代码了。
本来要讲handleActions的实现了,但是在这之前,我们必须先讲一下handleAction,对,你仔细看,没有s
五、认识与手写实现handleAction
5.1 用法
看下使用方式
const incrementReducer = handleAction(INCREMENT, (state, action) => {
return {counter: state.counter + action.payload}
}, initialState);
可以看出来,跟handleActions的区别 就是,handleAction生成的reducer是专门来处理一个action的。
5.2 原理实现
如果你看过redux原理的话(如果你没看过的话,推荐你去看下我之前的文章Redux 源码解析系列(一) -- Redux的实现思想),相信你应该知道reducer(state,action)返回的结果是一个新的state,然后这个新的state会和旧的state进行对比,如果发现两者不一样的话,就会重新渲染使用了state的组件,并且把新的state赋值给旧的state.
也就是说handleAction()返回一个reducer函数,然后incrementReducer()返回一个新的state。
先实现返回一个reducer函数
function handleAction(type, callback) {
return (state, action) => {
};
}
接下来应当是执行reducer(state,action)是时候返回state,也就是执行下面返回的这个
(state, action) => {
};
而其实就是执行callback(state) 然后返回一个新的 state
function handleAction(type, callback) {
return (state, action) => {
return callback(state)
};
}
或许你会有疑问,为什么要这么搞,而不直接像下面这样,就少了一层包含。
function handleAction(state,type, callback) {
return callback(state)
}
这才是它的巧妙之处。它在handleAction()返回的reducer()时,可不一定会执行callback(state),只有handleAction传入的type跟reducer()中传入的action.type匹配到了才会执行,否则就直接return state。表示没有任何处理
function handleAction(type, callback) {
return (state, action) => {
return callback(state)
};
}
因此我们需要多加一层判断
function handleAction(type, callback) {
return (state, action) => {
if (action.type !== type) {
return state;
}
return callback(state)
};
}
多么完美啊!
好了现在我们来实现下handleActions
六、handleActions原理实现
function handleActions(handlers, defaultState) {
const reducers = Object.keys(handlers).map(type => {
return handleAction(type, handlers[type]);
});
const reducer = reduceReducers(...reducers)
return (state = defaultState, action) => reducer(state, action)
}
看,就这几行代码,是不是很简单,不过应该不好理解,不过没关系,我依旧将它讲得粗俗易懂。
我们拿上面用到的例子来讲好了
var reducer = handleActions({
[INCREMENT]: (state, action) => ({
counter: state.counter + action.payload
}),
[DECREMENT]: (state, action) => ({
counter: state.counter - action.payload
})
},initstate)
{
[INCREMENT]: (state, action) => ({
counter: state.counter + action.payload
}),
[DECREMENT]: (state, action) => ({
counter: state.counter - action.payload
})
}
上面这个对象,经过下面的代码之后
const reducers = Object.keys(handlers).map(type => {
return handleAction(type, handlers[type]);
});
返回的reducer,其实就是
[
handleAction(INCREMENT,(state, action) => ({
counter: state.counter + action.payload
})),
handleAction(DECREMENT,(state, action) => ({
counter: state.counter + action.payload
})),
]
为什么要变成一个handleAction的数组,
我大概想到了,是想每次dispatch(action)的时候,就要遍历去执行这个数组中的所有handleAction。
那岂不是每个handleAction返回的reducer都要执行? 确实,但是别忘了我们上面讲到的,如果handleAction 判断到 type和action.type 是不会对state进行处理的而是直接返回state
function handleAction(type, callback) {
return (state, action) => {
if (action.type !== type) {
return state;
}
return callback(state)
};
}
没有即使每个 handleAction 都执行了也没关系
那应该怎么遍历执行,用map,forEach? 不,都不对。我们看回源码
function handleActions(handlers, defaultState) {
const reducers = Object.keys(handlers).map(type => {
return handleAction(type, handlers[type]);
});
const reducer = reduceReducers(...reducers)
return (state = defaultState, action) => reducer(state, action)
}
使用了
const reducer = reduceReducers(...reducers)
用了reduceReducers这个方法,顾名思义,看这方法名,意思就是用reduce这个来遍历执行reducers这个数组。也就是这个数组。
[
handleAction(INCREMENT,(state, action) => ({
counter: state.counter + action.payload
})),
handleAction(DECREMENT,(state, action) => ({
counter: state.counter + action.payload
})),
]
我们看下reduceReducers的内部原理
function reduceReducers(...args) {
const reducers = args;
return (prevState, value) => {
return reducers.reduce((newState, reducer, index) => {
return reducer(newState, value);
}, prevState);
};
};
我们发现将reducers这个数组放入reduceReducers,然后执行reduceReducers,就会返回
(prevState, value) => {
return reducers.reduce((newState, reducer, index) => {
return reducer(newState, value);
}, prevState);
};
这个方法,也就是说执行这个方法就会 执行
return reducers.reduce((newState, reducer, index) => {
return reducer(newState, value);
}, prevState);
也就是会使用reduce遍历执行reducers,为什么要用reduce来遍历呢?
这是因为需要把上一个handleAction执行后返回的state传递给下一个。
这个思想有一点我们之间之前讲的关于compose函数的思想,感兴趣的话,可以去看一下【前端进阶之认识与手写compose方法】
function handleActions(handlers, defaultState) {
const reducers = Object.keys(handlers).map(type => {
return handleAction(type, handlers[type]);
});
const reducer = reduceReducers(...reducers)
return (state = defaultState, action) => reducer(state, action)
}
现在也就是说这里的reducer是reduceReducers(...reducers)返回的结果,也就
reducer = (prevState, value) => {
return reducers.reduce((newState, reducer, index) => {
return reducer(newState, value);
}, prevState);
};
而handleActions返回
(state = defaultState, action) => reducer(state, action)
也就是说handleActions其实是返回这样一个方法。
(state = defaultState, action) => {
return reducers.reduce((newState, reducer, index) => {
return reducer(newState, value);
}, state);
}
好家伙,在handleAction之间利用reduce来传递state,真是个好方法,学到了。
贴一下github 的redux-action的源码地址,感兴趣的朋友可以亲自去阅读一下,毕竟本文是做了简化的 redux-actions:https://github.com/redux-utilities/redux-actions
最后
文章首发于公众号《前端阳光》,欢迎加入技术交流群。
参考文章
- 【React系列---FSA知识】https://segmentfault.com/a/1190000010113847
- 【Redux-actions 的用法】https://zhuanlan.zhihu.com/p/273569290
- 【前端进阶之认识与手写compose方法】
- Redux 源码解析系列(一) -- Redux的实现思想)
推荐使用并手写实现redux-actions原理的更多相关文章
- 手写一个Redux,深入理解其原理
Redux可是一个大名鼎鼎的库,很多地方都在用,我也用了几年了,今天这篇文章就是自己来实现一个Redux,以便于深入理解他的原理.我们还是老套路,从基本的用法入手,然后自己实现一个Redux来替代源码 ...
- 学习手写vue,理解原理
class Compiler{ constructor(el,vm){ // 判断el属性 是不是 一个元素, 如果不是就获取 this.el = this.isElementNode(el)?el: ...
- 手写 redux 和 react-redux
1.手写 redux redux.js /** * 手写 redux */ export function createStore(reducer) { // 当前状态 let currentStat ...
- React深入 - 手写redux api
简介: 手写实现redux基础api createStore( )和store相关方法 api回顾: createStore(reducer, [preloadedState], enhancer) ...
- 1 手写Java ArrayList核心源码
手写ArrayList核心源码 ArrayList是Java中常用的数据结构,不光有ArrayList,还有LinkedList,HashMap,LinkedHashMap,HashSet,Queue ...
- 30个类手写Spring核心原理之环境准备(1)
本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...
- 手写redux方法以及数组reduce方法
reduce能做什么? 1)求和 2)计算价格 3)合并数据 4)redux的compose方法 这篇文章主要内容是什么? 1)介绍reduce的主要作用 2)手写实现reduce方法 0)了解red ...
- 一起学习造轮子(二):从零开始写一个Redux
本文是一起学习造轮子系列的第二篇,本篇我们将从零开始写一个小巧完整的Redux,本系列文章将会选取一些前端比较经典的轮子进行源码分析,并且从零开始逐步实现,本系列将会学习Promises/A+,Red ...
- 手写一个React-Redux,玩转React的Context API
上一篇文章我们手写了一个Redux,但是单纯的Redux只是一个状态机,是没有UI呈现的,所以一般我们使用的时候都会配合一个UI库,比如在React中使用Redux就会用到React-Redux这个库 ...
随机推荐
- python—数据类型和变量
在python中,能够直接处理的数据类型和变量有整数.浮点数.字符串.布尔值.空值.变量. 一.整数 1.python可处理任意大小的整数,包括负整数,在程序中的表示方法与在数学中的方法一样.例如:0 ...
- 网络基础:ip地址
原文链接:http://blog.51cto.com/xiexiaojun/1882088 很棒的总结,概念+例题很清晰
- JZOJ2020年8月11日提高组T3 页
JZOJ2020年8月11日提高组T3 页 题目 Description 战神阿瑞斯听说2008年在中华大地上,将举行一届规模盛大的奥林匹克运动会,心中顿觉异常兴奋,他想让天马在广阔的天空上,举行一场 ...
- Spring Boot 2.x 多数据源配置之 JPA 篇
场景假设:现有电商业务,商品和库存分别放在不同的库 配置数据库连接 app: datasource: first: driver-class-name: com.mysql.cj.jdbc.Drive ...
- Django之数据库--ORM
一.建立数据库模型类 1.在model里创建模型类.(继承models.Model) from django.db import models # Create your models here. c ...
- 区块链知识博文1: 共识算法之争(PBFT,Raft,PoW,PoS,DPoS,Ripple)
注:这是本人读到的关于共识算法最全和最好的分享博文,系统的介绍了拜占庭容错技术以及共识算法的原理和常用共识算法,原文链接请见后. 目录 一.拜占庭容错技术(Byzantine Fault Tolera ...
- PyQt(Python+Qt)学习随笔:QTabWidget选项卡部件移除选项卡的removeTab和clear方法
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 要从一个QTabWidget中去除选项卡,可用使用removeTab和clear方法. 1.移除选项 ...
- 第8.28节 Python中使用__setattr__定义实例变量和实例方法
一. 引言 根据前面章节介绍的内容,我们知道实例变量.实例方法的定义可以通过以下方法进行: 在类体中直接定义实例变量.实例方法: 在实例方法中定义实例变量.实例方法: 在类体外调用方使用赋值语句赋值定 ...
- [Windows] 在 Microsoft Docs 网站中挖掘 MVVM 的各种学习资源
最近写了一些 MVVM 框架的文章,翻了一些 Microsoft Docs 的文档,顺便就对 MVVM 本身来了兴致,想看看更多当年相关的文档.在 MVVM 出现后十多年,我在不同的场合见到过多种 M ...
- 响应式网站css reset
响应式网站 css reset /* core.css v1.1 | MIT License | corecss.io */ html { font-family: sans-serif; font- ...