原文链接

middleware 的由来

在业务中需要打印每一个 action 信息来调试,又或者希望 dispatch 或 reducer 拥有异步请求的功能。面对这些场景时,一个个修改 dispatch 或 reducer 代码有些乏力,我们需要一个可组合的、自由增减的插件机制,Redux 借鉴了 Koa 中 middleware 的思想,利用它我们可以在前端应用中便捷地实现如日志打印、异步请求等功能。

比如在项目中,进行了如下调用后,redux 就集成了 thunk 函数调用以及打印日志的功能。

import thunk from 'redux-thunk'
import logger from '../middleware/logger'
const enhancer = applyMiddleware(thunk, logger), // 以 redux-thunk、logger 中间件为例介绍中间件的使用
const store = createStore(rootReducer, enhancer)

下面追本溯源,来分析下源码。

applyMiddleware 调用入口

export default function createStore(reducer, preloadedState, enhancer) {
// 通过下面代码可以发现,如果 createStore 传入 2 个参数,第二个参数相当于就是 enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
return enhancer(createStore)(reducer, preloadedState)
}
...
}

由上述 createStore 源码发现,applyMiddleware 会进行 applyMiddleware(thunk, logger)(createStore)(reducer, preloadedState) 的调用。

applyMiddleware 源码如下

export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = store.dispatch
let chain = [] const middlewareAPI = {
getState: store.getState, // 调用 redux 原生方法,获取状态
dispatch: (...args) => dispatch(...args) // 调用 redux 原生 dispatch 方法
}
// 串行 middleware
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch) return {
...store,
dispatch // 返回加工过的 dispatch
}
}
}

可以发现 applyMiddleware 的作用其实就是返回加工过的 dispatch,下面会着重分析 middlewares 是如何串行起来的以及 dispatch 是如何被加工的。

串行 middleware

const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)

观察上述代码后发现每个 middleware 都会传入参数 middlewareAPI,来看下中间件 logger 的源码 以及 redux-thunk 的源码, 发现中间件接受的第一个参数正是 ({ dispatch, getState })

// logger 源码
export default ({ dispatch, getState }) => next => action => {
console.log(action)
return next(action) // 经 compose 源码分析,此处 next 为 Store.dispatch
}
// redux-thunk 源码
export default ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch)
}
return next(action) // 此处 next 为 logger 中间件返回的 (action) => {} 函数
}

dispatch 是如何被加工的

接着上个小节,在 dispatch = compose(...chain)(store.dispatch) 中发现了 compose 函数,来看下 compose 的源码

export default function compose(...funcs) {
// ...
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

compose 源码中的 funcs.reduce((a, b) => (...args) => a(b(...args))) 算是比较重要的一句,它的作用是返回组合参数后的函数,比如 compose(f, g, h) 等价于 (...args) => f(g(h(...args))),效果图如下所示,调用 this.props.dispatch() 后,会调用相应的中间件,最终会调用 redux 原生的 store.dispatch(),并且可以看到中间件调用的形式类似数据结构中的栈(先进后出)。

拿上个小节提到的 logger、redux-thunk 中间件为例,其 middleware 的内部串行调用方式如下,从而完成了 dispatch 功能的增强(支持如 this.props.dispatch(func) 的调用以及日志功能)。具体可以看 项目中的运用

action => {
if (typeof action === 'function') {
return action(dispatch)
}
return (action => {
console.log(action)
return store.dispatch(action)
})(action)
}

参考文献

深入React技术栈

redux middleware 源码分析的更多相关文章

  1. Redux源码分析之createStore

    接着前面的,我们继续,打开createStore.js, 直接看最后, createStore返回的就是一个带着5个方法的对象. return { dispatch, subscribe, getSt ...

  2. Redux源码分析之applyMiddleware

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  3. Redux源码分析之compose

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  4. 正式学习React(四) ----Redux源码分析

    今天看了下Redux的源码,竟然出奇的简单,好吧.简单翻译做下笔记: 喜欢的同学自己可以去github上看:点这里 createStore.js import isPlainObject from ' ...

  5. Redux源码分析之基本概念

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  6. Redux源码分析之bindActionCreators

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  7. Redux源码分析之combineReducers

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  8. Koa源码分析(三) -- middleware机制的实现

    Abstract 本系列是关于Koa框架的文章,目前关注版本是Koa v1.主要分为以下几个方面: Koa源码分析(一) -- generator Koa源码分析(二) -- co的实现 Koa源码分 ...

  9. 大熊君大话NodeJS之 ------ Connect中间件第二季(源码分析)

    一,开篇分析 大家好,大熊君又回来了,今天这篇文章主要是对"Connect"中间件以及相关辅助中间件,做一个源码分析系列,我想上一篇文章大家也看了, 介绍了使用方式及用途,而这篇也 ...

随机推荐

  1. CCF系列之日期计算(201509-2)

    试题编号: 201509-2 时间限制: 1.0s 内存限制: 256.0MB 问题描述 给定一个年份y和一个整数d,问这一年的第d天是几月几日? 注意闰年的2月有29天.满足下面条件之一的是闰年: ...

  2. 让自己写的项目支持Cocoapods管理

    学会使用别人的 Pods 依赖库以后, 你一定对创建自己的依赖库很有兴趣吧,现在我们一起来制作自己的Pods依赖库. 1.创建自己的 github 仓库 上图中标识出了6处地方 Repository ...

  3. maven项目使用jacoco插件检测代码覆盖率详细配置

    使用maven构建项目(java项目或者web项目都可以) jacoco插件的配置参考官方网址:http://www.eclemma.org/jacoco/trunk/doc/maven.html ( ...

  4. python_virtualenvwrapper安装与使用

    如何创建当前python版本虚拟环境? 1. 安装virtualenv pip install -i https://pipy.doubanio.com/simple virtualenv 2. 创建 ...

  5. 国内不谈java

    今天晚上在整理电脑,不知道怎么回事,电脑里面放着一篇文章.打开一看写的挺好的,现在就贴出来,望共勉.   国内不谈java--会有千万人跳出来和你争嘴的.越是如此,我越是不忍心不说出来,越是不不忍心看 ...

  6. error_reporting

    有关error_reporting()函数: error_reporting() 设置 PHP 的报错级别并返回当前级别. ; 错误报告是按位的,或者将数字加起来得到想要的错误报告等级. ; E_AL ...

  7. lvs_dr

    lvs_dr 实验需求(4台虚拟机) eth0 192.168.1.110 单网卡 client(可以使用windows浏览器代替,但会有缓存影响) eth0 192.168.1.186 单网卡 di ...

  8. SQL语句-SELECT语句

    select语句 Select语句是指从现存的一个或多个表中查看满足条件的数据 Select语句常规用法: 查看表中所有数据 Select * from students; 查看所有的sid和snam ...

  9. 将Flask应用程序部署在nginx,tornado的简单方法

    来自:http://www.xuebuyuan.com/618750.html 在网上搜索了一下部署flask应用的方法,大部分是用wsgi部署在nginx上面,部署了很久,都没有成功,可能是我领悟能 ...

  10. python的logging模块

    python提供了一个日志处理的模块,那就是logging 导入logging模块使用以下命令: import logging logging模块的用法: 1.简单的将日志打印到屏幕上 import ...