摘要:最后看一下这时候执行返回,如下调用执行循序调用第层中间件返回即调用第层中间件返回即调用根返回即调用一个例子读懂上文提到是个柯里化函数,可以看成是将所有函数合并成一个函数并返回的函数。
由于一直用业界封装好的如redux-logger、redux-thunk此类的中间件,并没有深入去了解过redux中间件的实现方式。正好前些时间有个需求需要对action执行时做一些封装,于是借此了解了下Redux Middleware的原理。* 中间件概念
首先简单提下什么是中间件,该部分与下文关系不大,可以跳过。来看眼这个经典的图。
不难发现:
不使用middleware时,在dispatch(action)时会执行rootReducer,并根据action的type更新返回相应的state。
而在使用middleware时,简言之,middleware会将我们当前的action做相应的处理,随后再交付rootReducer执行。
简单实现原理比如现有一个action如下:
function getData() { return { api: "/cgi/getData", type: [GET_DATA, GET_DATA_SUCCESS, GET_DATA_FAIL] } }
我们希望执行该action时可以发起相应请求,并且根据请求结果由定义的type匹配到相应的reducer,那么可以自定义方法处理该action,因此该方法封装成中间件之前可能是这样的:
async function dispatchPre(action, dispatch) { const api = action.api; const [ fetching_type, success_type, fail_type] = action.type; // 拉取数据 const res = await request(api); // 拉取时状态 dispatch({type: fetching_type}); // 成功时状态 if (res.success) { dispatch({type: success_type, data: res.data}); console.log("GET_SUCCESS"); } // 失败时状态 if (res.fail) { dispatch({type: fail_type}); console.log("GET_FAIL"); }; } // 调用: dispatchPre(action(), dispatch)
那如何封装成中间件,让我们在可以直接在dispatch(action)时就做到这样呢?可能会首先想到改变dispatch指向
// 储存原来的dispatch const dispatch = store.dispatch; // 改变dispatch指向 store.dispatch = dispatchPre; // 重命名 const next = dispatch;
截止到这我们已经了解了中间件的基本原理了~
源码分析了解了基本原理能有助于我们更快地读懂middleware的源码。
业务中,一般我们会这样添加中间件并使用。
createStore(rootReducer, applyMiddleware.apply(null, [...middlewares]))
接下来我们可以重点关注这两个函数createStore、applyMiddleware
CreateStore// 摘至createStore export function createStore(reducer, rootState, enhance) { ... if (typeof enhancer !== "undefined") { if (typeof enhancer !== "function") { throw new Error("Expected the enhancer to be a function.") } /* 若使用中间件,这里 enhancer 即为 applyMiddleware() 若有enhance,直接返回一个增强的createStore方法,可以类比成react的高阶函数 */ return enhancer(createStore)(reducer, preloadedState) } ... }ApplyMiddleware
再看看applyMiddleware做了什么,applyMiddleware函数非常简单,就十来行代码,这里将其完整复制出来。
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } // 1、将store对象的基本方法传递给中间件并依次调用中间件 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 2、改变dispatch指向,并将最初的dispatch传递给compose dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }执行步骤
根据源码,我们可以将其主要功能按步骤划分如下:
1、依次执行middleware。
将middleware执行后返回的函数合并到一个chain数组,这里我们有必要看看标准middleware的定义格式,如下
export default store => next => action => {} // 即 function (store) { return function(next) { return function (action) { return {} } } }
那么此时合并的chain结构如下
[ ..., function(next) { return function (action) { return {} } } ]
2、改变dispatch指向。
想必你也注意到了compose函数,compose函数如下:
[...chain].reduce((a, b) => (...args) => a(b(...args)))
实际就是一个柯里化函数,即将所有的middleware合并成一个middleware,并在最后一个middleware中传入当前的dispatch。
*compose可能会看得有点蒙,不理解柯里化函数的同学可以跳到一个例子读懂compose先了解下。
// 假设chain如下: chain = [ a: next => action => { console.log("第1层中间件") return next(action) } b: next => action => { console.log("第2层中间件") return next(action) } c: next => action => { console.log("根dispatch") return next(action) } ]
调用compose(...chain)(store.dispatch)后返回a(b(c(dispatch)))。
可以发现已经将所有middleware串联起来了,并同时修改了dispatch的指向。
最后看一下这时候compose执行返回,如下
dispatch = a(b(c(dispatch))) // 调用dispatch(action) // 执行循序 /* 1. 调用 a(b(c(dispatch)))(action) __print__: 第1层中间件 2. 返回 a: next(action) 即b(c(dispatch))(action) 3. 调用 b(c(dispatch))(action) __print__: 第2层中间件 4. 返回 b: next(action) 即c(dispatch)(action) 5. 调用 c(dispatch)(action) __print__: 根dispatch 6. 返回 c: next(action) 即dispatch(action) 7. 调用 dispatch(action) */*一个例子读懂compose
上文提到compose是个柯里化函数,可以看成是将所有函数合并成一个函数并返回的函数。
例如先定义3个方法
function A(x){ return x + "a" } function B(y){ return y + "b" } function C(){ return "c" } var d = [...A, b, C].reduce((a, b) => (d) => {console.log(d, a, b); a(b(d))}) d // 打印d // f (d) { console.log(d, a, b); return a(b(d)) } d("d") // 调用d /* * d * f(d) { console.log(d, a, b); return a(b(d)) } * f C() { return "c" } */ /* * c * f A(x) { return x + "a" } * f B(y) { return y + "b" } */
不难发现,使用闭包,在调用d的时候,将a、b函数储存在了内存中,调用时会依次将数组从右至左的函数返回做为参数传递给下一个函数使用
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/102239.html
摘要:另外,内置的函数在经过一系列校验后,触发,之后被更改,之后依次调用监听,完成整个状态树的更新。总而言之,遵守这套规范并不是强制性的,但是项目一旦稍微复杂一些,这样做的好处就可以充分彰显出来。 这一篇是接上一篇react进阶漫谈的第二篇,这一篇主要分析redux的思想和应用,同样参考了网络上的大量资料,但代码同样都是自己尝试实践所得,在这里分享出来,仅供一起学习(上一篇地址:个人博客/s...
摘要:另外,内置的函数在经过一系列校验后,触发,之后被更改,之后依次调用监听,完成整个状态树的更新。总而言之,遵守这套规范并不是强制性的,但是项目一旦稍微复杂一些,这样做的好处就可以充分彰显出来。 这一篇是接上一篇react进阶漫谈的第二篇,这一篇主要分析redux的思想和应用,同样参考了网络上的大量资料,但代码同样都是自己尝试实践所得,在这里分享出来,仅供一起学习(上一篇地址:个人博客/s...
摘要:在函数式编程中,异步操作修改全局变量等与函数外部环境发生的交互叫做副作用通常认为这些操作是邪恶肮脏的,并且也是导致的源头。 注:这篇是17年1月的文章,搬运自本人 blog... https://github.com/BuptStEve/... 零、前言 在上一篇中介绍了 Redux 的各项基础 api。接着一步一步地介绍如何与 React 进行结合,并从引入过程中遇到的各个痛点引出 ...
摘要:的中间件主要是通过模块实现的。返回的也是一个对象这个其实就是,各个中间件的最底层第三层的哪个函数组成的圆环函数构成的这就是对源码的一个整体解读,水平有限,欢迎拍砖。后续的源码解读和测试例子可以关注源码解读仓库 applyMiddleware源码解析 中间件机制在redux中是强大且便捷的,利用redux的中间件我们能够实现日志记录,异步调用等多种十分实用的功能。redux的中间件主要是...
阅读 4548·2021-09-10 11:22
阅读 529·2019-08-30 11:17
阅读 2564·2019-08-30 11:03
阅读 430·2019-08-29 11:18
阅读 3455·2019-08-28 17:59
阅读 3218·2019-08-26 13:40
阅读 3157·2019-08-26 10:29
阅读 1136·2019-08-26 10:14