Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

手写redux-actions核心原理,再也不怕面试官问我redux-actions核心原理 #11

Open
Sunny-lucking opened this issue Apr 20, 2022 · 0 comments

Comments

@Sunny-lucking
Copy link
Owner

一、前言

为什么介绍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,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-actionshttps://github.com/redux-utilities/redux-actions

最后

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

参考文章

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant