推荐使用并手写实现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。

基本上就是只有用到createActionhandleActionshandleAction

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

三、 认识与手写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,};
        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) 看下结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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

五、认识与手写实现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,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,action) => {
        
      return callback(state)
    };
}

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

function handleAction(type,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,initstate)
{
    [INCREMENT]: (state,action) => ({
      counter: state.counter - action.payload
    })
}

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

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

返回的reducer,其实就是

[
  handleAction(INCREMENT,action) => ({
      counter: state.counter + action.payload
  })),handleAction(DECREMENT,]

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

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

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

function handleAction(type,action) => {
        if (action.type !== type) {
            return state;
        }
        return callback(state)
    };
}

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

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

function handleActions(handlers,action)
}

使用了

const reducer = reduceReducers(...reducers)

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

[
  handleAction(INCREMENT,]

我们看下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,index) => {
        return reducer(newState,value);
    },prevState);
};

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

return reducers.reduce((newState,prevState);

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

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

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

function handleActions(handlers,action)
}

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

reducer = (prevState,prevState);
};

而handleActions返回

(state = defaultState,action)

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

(state = defaultState,action) => {
    return reducers.reduce((newState,state);
}

好家伙,在handleAction之间利用reduce来传递state,真是个好方法,学到了。

贴一下github 的redux-action的源码地址,感兴趣的朋友可以亲自去阅读一下,毕竟本文是做了简化的 redux-actionshttps://github.com/redux-utilities/redux-actions

最后

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

参考文章

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom