推荐使用并手写实现Redux-actions原理

开发 前端
第一次见到主要是接手公司原有的项目,发现有之前的大佬在处理redux的时候引入了它。发现也确实 使得 在对redux的处理上方便了许多,而我为了更好地使用一个组件或者插件,都会去去尝试阅读源码并写成文章 ,这个也不例外。发现也确实有意思,推荐大家使用redux的时候也引入redux-actions。

[[358493]]

一、前言

为什么介绍redux-actions呢?

第一次见到主要是接手公司原有的项目,发现有之前的大佬在处理redux的时候引入了它。

发现也确实 使得 在对redux的处理上方便了许多,而我为了更好地使用一个组件或者插件,都会去去尝试阅读源码并写成文章 ,这个也不例外。

发现也确实有意思,推荐大家使用redux的时候也引入redux-actions

在这里就介绍一下其使用方式,并且自己手写实现一个简单的redux-actions

二、介绍

学习 redux 中,总觉得 action 和 reducer 的代码过于呆板,比如

2.1 创建action

  1. let increment = ()=>({type:"increment"}) 

2.2 reducer

  1. let reducer = (state,action)=>{ 
  2.     switch(action.type){ 
  3.       case "increment":return {count:state.count+1};break; 
  4.       case "decrement":return {count:state.count-1};break; 
  5.       default:return state; 
  6.     } 

2.3 触发action

  1. dispatch(increment()) 

综上所示,我们难免会觉得 increment 和 reducer 做一个小 demo 还行,遇到逻辑偏复杂的项目后,项目管理维护就呈现弊端了。所以最后的方式就是将它们独立出来,同时在 reducer 中给与开发者更多的主动权,不能仅停留在数字的增增减减。

redux-actions主要函数有createAction、createActions、handleAction、handleActions、combineActions。

基本上就是只有用到createAction,handleActions,handleAction

所以这里我们就只讨论这三个个。

三、 认识与手写createAction()

3.1 用法

一般创建Action方式:

  1. let increment = ()=>({type:"increment"}) 
  2. let incrementObj = increment();// { type:"increment"

使用createAction 创建 action

  1. import { createAction } from 'redux-actions'
  2. const increment = createAction('increment'); 
  3. let incrementObj = increment();// { type:"increment"
  4. let objincrement = increment(10);// {type:"increment",paylaod:10} 

我们可以看到

  1. let increment = ()=>({type:"increment"}) 
  2. let incrementObj = increment();// { type:"increment"

  1. const increment = createAction('increment'); 
  2. let incrementObj = increment();// { type:"increment"

是等效的,那为什么不直接用传统方式呢?

不难发现有两点:

  1. 传统方式,需要自己写个函数来返回incrementObj,而利用封装好的createAtion就不用自己写函数
  2. 传统方式,在返回的incrementObj若是有payload需要自己添加上去,这是多么麻烦的事情啊,你看下面的代码,如此的不方便。但是用了createAction返回的increment,我们添加上payload,十分简单,直接传个参数,它就直接把它作为payload的值了。
  1. let increment = ()=>({type:"increment",payload:123}) 

3.2 原理实现

我们先实现个简单,值传入 type参数的,也就是实现下面这段代码的功能

  1. const increment = createAction('increment'); 
  2. let incrementObj = increment();// { type:"increment"

我们发现createAction('increment')()才返回最终的action对象。这不就是个柯里化函数吗?

所以我们可以非常简单的写出来,如下面代码所示,我们把type类型当作action对象的一个属性了

  1. function createAction(type) { 
  2.     return () => { 
  3.         const action = { 
  4.             type 
  5.         }; 
  6.         return action
  7.     }; 

好了现在,现在实现下面这个功能,也就是有payload的情况

  1. const increment = createAction('increment'); 
  2. let objincrement = increment(10);// {type:"increment",paylaod:10} 

很明显,这个payload是 在createAction('increment')返回的函数的参数,所以我们轻而易举地给action添加上了payload。

  1. function createAction(type) { 
  2.     return (payload) => { 
  3.         const action = { 
  4.             type, 
  5.             payload 
  6.         }; 
  7.         return action
  8.     }; 

但是像第一种情况我们是不传payload的,也就是说返回的action是不希望带有payload的,但是这里我们写成这样就是 默认一定要传入payload的了。

所以我们需要添加个判断,当不传payload的时候,action就不添加payload属性。

  1. function createAction(type) { 
  2.     return (payload) => { 
  3.         const action = { 
  4.             type, 
  5.         }; 
  6.         if(payload !== undefined){ 
  7.             action.payload = payload 
  8.         } 
  9.         return action
  10.     }; 

在实际项目中我更喜欢下面这种写法,但它是等价于上面这种写法的

  1. function createAction(type) { 
  2.     return (payload) => { 
  3.         const action = { 
  4.             type, 
  5.             ...payload?{payload}:{} 
  6.         }; 
  7.         return action
  8.     }; 

其实createAction的参数除了type,还可以传入一个回调函数,这个函数表示对payload的处理。

  1. const increment = createAction('increment'); 
  2. let objincrement = increment(10);// {type:"increment",paylaod:10} 

像上面的代码所示,我们希望的是传入10之后是返回的action中的payload是我们传入的2倍数

  1. const increment = createAction('increment',(t)=> t * 2); 
  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; };}

  1. function createAction(type,payloadCreator) { 
  2.     return (payload) => { 
  3.         const action = { 
  4.             type, 
  5.         }; 
  6.         if(payload !== undefined){ 
  7.             action.payload = payloadCreator(payload) 
  8.         } 
  9.         return action
  10.     }; 

太简单了吧!但是我们又犯了前边同样的错误,就是我们使用createAction的时候,不一定会传入payloadCreator这个回调函数,所以我们还需要判断下

  1. function createAction(type,payloadCreator) { 
  2.     return (payload) => { 
  3.         const action = { 
  4.             type, 
  5.         }; 
  6.         if(payload !== undefined){ 
  7.             action.payload = payloadCreator?payloadCreator(payload):payload 
  8.         } 
  9.         return action
  10.     }; 

完美。

接下來看看 redux-action的 handleActions吧

四、认识handleActions

我们先看看传统的reducer是怎么使用的

  1. let reducer = (state,action)=>{ 
  2.     switch(action.type){ 
  3.       case "increment":return {count:state.count+1};break; 
  4.       case "decrement":return {count:state.count-1};break; 
  5.       default:return state; 
  6.     } 

再看看使用了handleActions

  1. const INCREMENT = "increment" 
  2. const DECREMENT = "decrement" 
  3. var reducer = handleActions({ 
  4.     [INCREMENT]: (state, action) => ({ 
  5.       counter: state.counter + action.payload 
  6.     }), 
  7.     [DECREMENT]: (state, action) => ({ 
  8.       counter: state.counter - action.payload 
  9.     }) 
  10. },initstate) 

这里大家不要被{[DECREMENT]:(){}} 的写法吓住哈,就是把属性写成变量了而已。

我们在控制台 console.log(reducer) 看下结果

图片

最后返回的就是一个 reducer 函数。

这样就实现了 reducer 中功能化的自由,想写什么程序,我们只要写在

  1. {[increment]:(state,action)=>{}}  

这个函数内就行,同时也可以把这些函数独立成一个文件,再引入进来就行

  1. import {increment,decrement}from "./reducers.js" 
  2. var initstate = {count:0} 
  3. var reducer = createReducer({ 
  4.     [INCREMENT]: increment, 
  5.     [DECREMENT]: decrement 
  6. },initstate) 

reducers.js

  1. //reducers.js 
  2. export let increment = (state,action)=>({counter: state.counter + action.payload}) 
  3. export let decrement = (state,action)=>({counter: state.counter - action.payload}) 

可见,

handleactions 可以简化 reducers 的写法 不用那么多 switch 而且可以把函数独立出来,这样reducer就再也不会有一大堆代码了。

本来要讲handleActions的实现了,但是在这之前,我们必须先讲一下handleAction,对,你仔细看,没有s

五、认识与手写实现handleAction

5.1 用法

看下使用方式

  1. const incrementReducer = handleAction(INCREMENT, (state, action) => { 
  2.   return {counter: state.counter + action.payload} 
  3. }, initialState); 

可以看出来,跟handleActions的区别 就是,handleAction生成的reducer是专门来处理一个action的。

5.2 原理实现

如果你看过redux原理的话(如果你没看过的话,推荐你去看下我之前的文章Redux 源码解析系列(一) -- Redux的实现思想),相信你应该知道reducer(state,action)返回的结果是一个新的state,然后这个新的state会和旧的state进行对比,如果发现两者不一样的话,就会重新渲染使用了state的组件,并且把新的state赋值给旧的state.

也就是说handleAction()返回一个reducer函数,然后incrementReducer()返回一个新的state。

先实现返回一个reducer函数

  1. function handleAction(type, callback) { 
  2.     return (state, action) => { 
  3.        
  4.     }; 

接下来应当是执行reducer(state,action)是时候返回state,也就是执行下面返回的这个

  1. (state, action) => { 
  2.        
  3. }; 

而其实就是执行callback(state) 然后返回一个新的 state

  1. function handleAction(type, callback) { 
  2.     return (state, action) => { 
  3.          
  4.       return callback(state) 
  5.     }; 

或许你会有疑问,为什么要这么搞,而不直接像下面这样,就少了一层包含。

  1. function handleAction(state,type, callback) { 
  2.     return callback(state) 

这才是它的巧妙之处。它在handleAction()返回的reducer()时,可不一定会执行callback(state),只有handleAction传入的type跟reducer()中传入的action.type匹配到了才会执行,否则就直接return state。表示没有任何处理

  1. function handleAction(type, callback) { 
  2.     return (state, action) => { 
  3.          
  4.       return callback(state) 
  5.     }; 

因此我们需要多加一层判断

  1. function handleAction(type, callback) { 
  2.     return (state, action) => { 
  3.         if (action.type !== type) { 
  4.             return state; 
  5.         } 
  6.         return callback(state) 
  7.     }; 

多么完美啊!

好了现在我们来实现下handleActions

六、handleActions原理实现

  1. function handleActions(handlers, defaultState) { 
  2.     const reducers = Object.keys(handlers).map(type => { 
  3.         return handleAction(type, handlers[type]); 
  4.     }); 
  5.     const reducer = reduceReducers(...reducers) 
  6.     return (state = defaultState, action) => reducer(state, action

看,就这几行代码,是不是很简单,不过应该不好理解,不过没关系,我依旧将它讲得粗俗易懂。

我们拿上面用到的例子来讲好了

  1. var reducer = handleActions({ 
  2.     [INCREMENT]: (state, action) => ({ 
  3.       counter: state.counter + action.payload 
  4.     }), 
  5.     [DECREMENT]: (state, action) => ({ 
  6.       counter: state.counter - action.payload 
  7.     }) 
  8. },initstate) 

  1.     [INCREMENT]: (state, action) => ({ 
  2.       counter: state.counter + action.payload 
  3.     }), 
  4.     [DECREMENT]: (state, action) => ({ 
  5.       counter: state.counter - action.payload 
  6.     }) 
  7. } 

上面这个对象,经过下面的代码之后

  1. const reducers = Object.keys(handlers).map(type => { 
  2.         return handleAction(type, handlers[type]); 
  3.     }); 

返回的reducer,其实就是

  1.   handleAction(INCREMENT,(state, action) => ({ 
  2.       counter: state.counter + action.payload 
  3.   })), 
  4.   handleAction(DECREMENT,(state, action) => ({ 
  5.       counter: state.counter + action.payload 
  6.   })), 

为什么要变成一个handleAction的数组,

我大概想到了,是想每次dispatch(action)的时候,就要遍历去执行这个数组中的所有handleAction。

那岂不是每个handleAction返回的reducer都要执行?确实,但是别忘了我们上面讲到的,如果handleAction 判断到 type和action.type 是不会对state进行处理的而是直接返回state

  1. function handleAction(type, callback) { 
  2.     return (state, action) => { 
  3.         if (action.type !== type) { 
  4.             return state; 
  5.         } 
  6.         return callback(state) 
  7.     }; 

没有即使每个 handleAction 都执行了也没关系

那应该怎么遍历执行,用map,forEach?不,都不对。我们看回源码

  1. function handleActions(handlers, defaultState) { 
  2.     const reducers = Object.keys(handlers).map(type => { 
  3.         return handleAction(type, handlers[type]); 
  4.     }); 
  5.     const reducer = reduceReducers(...reducers) 
  6.     return (state = defaultState, action) => reducer(state, action

使用了

  1. const reducer = reduceReducers(...reducers) 

用了reduceReducers这个方法,顾名思义,看这方法名,意思就是用reduce这个来遍历执行reducers这个数组。也就是这个数组。

  1.   handleAction(INCREMENT,(state, action) => ({ 
  2.       counter: state.counter + action.payload 
  3.   })), 
  4.   handleAction(DECREMENT,(state, action) => ({ 
  5.       counter: state.counter + action.payload 
  6.   })), 

我们看下reduceReducers的内部原理

  1. function reduceReducers(...args) { 
  2.     const reducers = args; 
  3.     return (prevState, value) => { 
  4.         return reducers.reduce((newState, reducer, index) => { 
  5.             return reducer(newState, value); 
  6.         }, prevState); 
  7.     }; 
  8. }; 

我们发现将reducers这个数组放入reduceReducers,然后执行reduceReducers,就会返回

  1. (prevState, value) => { 
  2.     return reducers.reduce((newState, reducer, index) => { 
  3.         return reducer(newState, value); 
  4.     }, prevState); 
  5. }; 

这个方法,也就是说执行这个方法就会 执行

  1. return reducers.reduce((newState, reducer, index) => { 
  2.         return reducer(newState, value); 
  3.     }, prevState); 

也就是会使用reduce遍历执行reducers,为什么要用reduce来遍历呢?

这是因为需要把上一个handleAction执行后返回的state传递给下一个。

这个思想有一点我们之间之前讲的关于compose函数的思想,感兴趣的话,可以去看一下【前端进阶之认识与手写compose方法】

  1. function handleActions(handlers, defaultState) { 
  2.     const reducers = Object.keys(handlers).map(type => { 
  3.         return handleAction(type, handlers[type]); 
  4.     }); 
  5.     const reducer = reduceReducers(...reducers) 
  6.     return (state = defaultState, action) => reducer(state, action

现在也就是说这里的reducer是reduceReducers(...reducers)返回的结果,也就

  1. reducer = (prevState, value) => { 
  2.     return reducers.reduce((newState, reducer, index) => { 
  3.         return reducer(newState, value); 
  4.     }, prevState); 
  5. }; 

而handleActions返回

  1. (state = defaultState, action) => reducer(state, action

也就是说handleActions其实是返回这样一个方法。

  1. (state = defaultState, action) => { 
  2.     return reducers.reduce((newState, reducer, index) => { 
  3.         return reducer(newState, value); 
  4.     }, 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的实现思想)

 

责任编辑:姜华 来源: 前端阳光
相关推荐

2021-12-01 06:40:32

Bind原理实现

2020-07-03 17:20:07

Redux前端代码

2021-11-30 06:56:58

CallApply函数

2018-03-06 15:05:30

数据库连接池连接管道

2020-10-23 09:26:57

React-Redux

2023-05-11 07:25:57

ReduxMiddleware函数

2022-11-15 17:07:40

开发自动化前端

2019-09-23 19:30:27

reduxreact.js前端

2020-12-03 08:14:45

Axios核心Promise

2020-11-02 09:35:04

ReactHook

2009-12-30 15:26:02

Silverlight

2017-03-02 10:49:37

推荐算法原理实现

2024-02-20 08:08:43

2020-12-10 08:24:40

线程池线程方法

2021-12-12 21:01:12

CSS 技巧PurgeCss

2019-11-26 08:00:00

GitHubGitHub ActiAzure

2024-01-24 18:50:21

WebFTP服务器

2024-02-01 08:12:15

ReducerContext状态

2021-07-16 07:57:34

ReduxDOM组件

2021-07-27 14:50:15

axiosHTTP前端
点赞
收藏

51CTO技术栈公众号