带你从头到尾系统地撸一遍Redux源码(二)

本文涉及的产品
简介: Redux中的中间件思想要想理解redux 的中间件思想, 我们先 看下compose这个文件做了什么, 这个文件其实做的事情十分简单。主要是用到函数式编程中的组合概念, 将多个函数调用组合成一个函数调用。

Redux中的中间件思想



要想理解redux 的中间件思想, 我们先 看下compose这个文件做了什么, 这个文件其实做的事情十分简单。主要是用到函数式编程中的组合概念, 将多个函数调用组合成一个函数调用。


其实主要是reduce 的这个api,为了方面大家理解, 我就简单手写下数组reduce 的实现:


image.png


ok 这东西其实就是个累加器按照某种方式呗。我们接下来就直接进入compose函数话不多说直接看代码:


// 参数是函数数组
export default function compose(...funcs: Function[]) {
  // 处理边界情况
  if (funcs.length === 0) {            return <T>(arg: T) => arg  }  // 数量为1 就没有组合的必要了  if (funcs.length === 1) {    return funcs[0]  }  // 主要是下面这一行代码, 你细品👌  return funcs.reduce((a, b) => (...args: any) => a(b(...args)))}
复制代码


OK, 我现在还是带大家分析下这行代码,同样是我举例子说明: 假设我们有这个3个函数:


funcs = [fa, fb, fc]


由于没有出初始值累加器的就是 fa 经过一次遍历后, accumulateur 变为下面的样子:

let  m= (...args) => fa(fb(...args))


在经过一次遍历后, 此时b =  fc  此时accumulateur 变为下面的样子:

**(...args) => m(fc(...args))**


我们将fc(...args) 看做一个整体带入上面m的函数  所以就变成了下面的样子

(...args)=> fa(fb(fc(...args)))


OK到这里 就大工告成了,fa, fb, fc 我们可以想象成 redux 的3个中间件, 按照顺序传进去,


当这个函数被调用的时候也会按照 fa, fb, fc 顺序调用。前面铺垫结束就直接开始分析? compose 是如何 和Redux 做结合的。


applyMiddleWare


我先把整体结构分析下, 和函数参数分下:


// applyMiddlerware 会使用“...”运算符将入参收敛为一个数组
export default function applyMiddleware(...middlewares) {
  // 它返回的是一个接收 createStore 为入参的函数
  return createStore => (...args) => {
    ......
  }
}
复制代码


createStore 就是 上面我们分析过的 创建数据中心Store  ,而 args  主要是有两个, 还是createStore 两个约定入参   一个是reducer, 一个是 initState。  


enhance-dispatch


接下来就是比较核心的, 改写dispacth, 为什么要改写dispatch,  还是举个例子说明。 不知道大家还记不记得 dispacth  接受的action 只能是对象, 如果不是对象的话会直接报类型错误。如图:


image.png


OK社区比较有名的redux-thunk  中间件, dispatch 可以接受一个函数, 难道是她绕过了我们的检查,那肯定不是,计算机肯定不会骗人的。 那只有一个原因。


应用了中间件的dispatch 和 没有用中间的dispatch 肯定是不等的,

dispatch = enhancer(dispacth) 肯定是增强的至于怎么个增强法 继续往下看。


const store = createStore(...args)
let chain = []
const middlewareAPI = {
    getState: store.getState,
    dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI)) // 绑定 {dispatch和getState}
dispatch = compose(...chain)(store.dispatch) 
复制代码


从代码中可以看到的第一步 其实就是将 getState 和 dispatch 作为中间件中的闭包使用, 有人会这里提问,这里为什么是匿名函数 ? 而不是store.dispatch ?  这里你可以自行先🤔下, 后面解答。


可能很多人看到这里还是很懵, 还是举个例子说明, 还记得上面的 fa , fb, fc 接下来 我就把他们展开。


function fa(store) {
  return function(next) {
    return function(action) {
      console.log('A middleware1 开始');
      next(action)
      console.log('B middleware1 结束');
    };
  };
}
function fb(store) {
  return function(next) {
    return function(action) {
      console.log('C middleware2 开始');
      next(action)
      console.log('D middleware2 结束');
    };
  };
}
function fc(store) {
  return function(next) {
    return function(action) {
      console.log('E middleware3 开始');
      next(action)
      console.log('F middleware3 结束');
    };
  };
}
复制代码


ok 我们一步一步分析, 看到底做了什么。


chain = middlewares.map(middleware => middleware(middlewareAPI)) 
复制代码


很显然 我们 的 middlewares = [fa, fb, fc] map 之后返回一个新的 chain 这时候的chain 应该是下面 这样子的:


chain = [ (next)=>(action)=>{...}, (next) => (action) => {...}, (next) => (action) => {...} ]


只不过chain中的每一个元素  都有getSate,dispatch 的闭包而已。

 

ok 继续往下走就到了compose 函数   还记得上面我们compose(fa, fb, fc ) 返回值是什么?


(...args)=> fa(fb(fc(...args)))


对的就是这个东西, 我们这里的chain 也是一样的, 所以这里的 dispatch 就变成了增强的dispatch 我们一起看下


dispatch = fa(fb(fc(store.dispacth)))


看到这里有人就问? 每一个中间件的next 和 原先的store.dispacth 有什么不同 ? 这和洋葱模型有什么关系?


那我就带你一步一步分析,   这里的dispatch 就是指的是当前的 fa(fb(fc(store.dispatch))) , 我们可以直接函数调用来分析,


fa 的参数 是 fb(fc(store.dispatch)) , 由于依赖fb, 所以调用fb, 然后发现fb 依赖的参数是 fc(store.dispacth), 紧接着又开始调用fc, ok 到这里终于结束了, 终于没有依赖了。 所以从上面的过程我们可以得到 next  其实是他上个中间件的副作用, 最后一个中间件的next 就是****store.dispatch。


副作用: 每个中间件的中的 (action )=> {...}


我用流程图表示整个洋葱模型流程:


image.png


当我调用dispatch 的时候, 先打印 E, 然后发现 next 是副作用fb, 然后调用副作用fb, 打印c,发现 next 竟然是 副作用fc ,再去调用fc, 打印A, next 这时候就是 store.dispacth, 调用结束, 打印 b, 然后打印D, 最后打印 F 。 这样的一系列操作是不是有点像洋葱模型, 如果我可以一层一层拨开你的心, 哈哈哈哈, 太骚了, 好了回归主题。还记得我之前提出的问题?


1.为什么dispacth 是匿名函数?


2. 为什么dispacth 一个action后, 还是返回action


问题1: 为什么dispatch 是是一个匿名函数 , 因为有的中间件原理的实现,并不会 next(action), 这时候需要肯定是增强的dispacth, redux-thunk 的执行原理, 就是当你传递一个函数, 直接调用这个函数,并把dispatch 的 权限 交给你自己处理。


function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      // 这个的dispatch 其实是增强的dispatch, 
      // 如果用store.dispatch如果还有其他中间件就丢失了      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}
复制代码


这里看下源码:


// 这个其实dispatch 其实 是个闭包let dispatch: Dispatch = () => {    throw new Error(        'Dispatching while constructing your middleware is not allowed. ' +        'Other middleware would not be applied to this dispatch.'    )}const middlewareAPI: MiddlewareAPI = {    getState: store.getState,    dispatch: (action, ...args) => dispatch(action, ...args)}const chain = middlewares.map(middleware => middleware(middlewareAPI))// compose 完之后将闭包更新成增强的dispatchdispatch = compose<typeof dispatch>(...chain)(store.dispatch)
复制代码


所以Redux 严格 意义上 并不算是洋葱模型, 他的洋葱模型是建立在你的每个中间件,都要next(action); 如果不 next(action) 其实就破坏了洋葱模型。


问题2: dispatch (action) 返回action, 就是为了方便下一个中间件的处理。



相关实践学习
基于函数计算一键部署掌上游戏机
本场景介绍如何使用阿里云计算服务命令快速搭建一个掌上游戏机。
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
9月前
|
JavaScript 算法 索引
面试被问到vue的diff算法原理,我不允许你回答不上来
面试被问到vue的diff算法原理,我不允许你回答不上来
35 0
|
2月前
|
开发框架 移动开发 前端开发
分享47个JavaScript框架源码,总有一款适合您
分享47个JavaScript框架源码,总有一款适合您
38 0
|
2月前
|
前端开发 JavaScript 算法
万万没想到,React 优先级队列的实现方式,跟我书里写的一模一样
万万没想到,React 优先级队列的实现方式,跟我书里写的一模一样
|
11月前
|
存储 算法 C语言
九分钟带你弄懂KMP算法【原理篇】
在一些寻找子串的问题中,我们常常使用的是BF算法,也就是暴力算法,这样做的时间复杂度通常都是O(N^2),且不能体现出算法的美妙之处
130 0
|
12月前
|
缓存 移动开发 JavaScript
vue面试提整理偏原理
vue面试提整理偏原理
51 0
|
存储 编解码 监控
一文详解|如何写出优雅的代码
和大家一起探讨一下优雅代码
120210 20
一文详解|如何写出优雅的代码
|
12月前
|
设计模式 人工智能 程序员
感觉自己的代码很乱?因为你不懂套路
编程教室开了这么久,已经有很多人从完全零基础的小白成为了会写代码的菜鸟程序员,能够自己独立开发程序。不过到此阶段,常常会遇到瓶颈,感觉功能可以实现
|
存储
《操作系统概论》第一遍阅读
前言: 《操作系统概论》从操作系统实现资源管理的观点出发,产生如何对计算机系统中的软硬件资源进行管理,要求我们理解操作系统要做什么,怎么样去做。学习操作系统概论是在米老师给我们讲完《信息资源管理》学习后按照老师所讲的方法来学习的,所以这次阅读,都是按照老师的指导一步一脚一脚印完成的。感觉很好。
|
JavaScript 前端开发 程序员
做为前端面试官,春招跳槽建议你把这20个vue题目搞懂
做为前端面试官,春招跳槽建议你把这20个vue题目搞懂
101 0
|
存储 自然语言处理 算法
刚学完c没掌握完的知识,不会学c++的时候还没搞懂吧?
c++既可用于基于过程的结构化程序设计,又可用于面向对象的程序设计,是一个功能强大的混合型程序设计语言。
113 0
刚学完c没掌握完的知识,不会学c++的时候还没搞懂吧?

热门文章

最新文章