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

@前端

1、前言

为何介绍redux-actions呢?git

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

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

发现也确实有意思,推荐你们使用redux的时候也引入redux-actionssegmentfault

在这里就介绍一下其使用方式,而且本身手写实现一个简单的redux-actions数组

2、介绍

在学习 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。spa

基本上就是只有用到createActionhandleActionshandleAction插件

因此这里咱们就只讨论这三个个。

3、 认识与手写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"}

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

不难发现有两点:

  1. 传统方式,须要本身写个函数来返回incrementObj,而利用封装好的createAtion就不用本身写函数
  2. 传统方式,在返回的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吧

4、认识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) 看下结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kfrdU9BQ-1608209103080)(https://imgkr2.cn-bj.ufileos.com/736f323e-c4de-4439-92f2-51adf00e9185.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=VnvrRa9sDm%252B5TI9cp1Gvl0GwXBc%253D&Expires=1608280295)]

最后返回的就是一个 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

5、认识与手写实现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

6、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-actionshttps://github.com/redux-utilities/redux-actions

最后

文章首发于公众号《前端阳光》,欢迎加入技术交流群。

参考文章

相关文章
相关标签/搜索