Redux和React-Redux的实现(三):中间件的原理和applyMiddleware、Thunk的实现
现在我们的Redux和React-Redux已经基本实现了,在Redux中,触发一个action,reducer立即就能算出相应的state,如果我要过一会才让reducer计算state呢怎么办?也就是我们如何实现异步的action呢?这里就要用到中间件(middleware)
1. 中间件(middleware)介绍
中间就是在action与reducer之间又加了一层,没有中间件的Redux的过程是:action -> reducer
,而有了中间件的过程就是action -> middleware -> reducer
,使用中间件我们可以对action也就是对dispatch方法进行装饰,我们可以用它来实现异步action、打印日志、错误报告等功能。
又是装饰器,没错,这块的好多东西都离不开装饰器模式,所以,设计模式很重要。
关于中间件,有很多框架或者是类库都使用了中间件,像express、koa、mongoose等都有使用。
2. Redux中间件的使用
我们可以使用Redux提供的applyMiddleware方法来使用一个或者是多个中间件,将它作为createStore的第二个参数传入即可,我们以Redux-Thunk为例
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(counter, applyMiddleware(thunk))
ReactDOM.render(
(
<Provider store={store}>
<App />
</Provider>
),
document.getElementById('root')
)
通过thunk中间件,我们就可以实现异步的action了。
export function addAsync(){
return dispatch => {
setTimeout(() => {
dispatch(add())
}, 2000);
}
}
想要实现中间件,我们首先有两个任务要做:
扩展createStore方法,使它可以接收第二个参数。
applyMiddleware方法的实现。
3. createStore方法的扩展
我们在createStore中加入第二个参数enhancer, 专业的解释应该叫增强器,叫middleware也可以的。
我们已经说过中间件的作用就是通过改变dispatch方法来改变数据流,所以我们这里直接用enhancer对createStore方法进行装饰。Redux的源码也是这么写的,哈哈哈哈,怎么和我想到的一模一样呢?因为我看了Redux的源码。。
export function createStore (reducer,enhancer) {
if (enhancer) {
return enhancer(createStore)(reducer)
}
let state = {}
let listeners = []
function getState () {
return state
}
function subscribe (listener) {
listeners.push(listener)
}
function dispatch (action) {
state = reducer(state, action)
listeners.forEach(listener => listener())
return action
}
dispatch({type: '@myRedux'})
return {getState, subscribe, dispatch}
}
高阶函数的写法,应该都能看懂了吧?前几篇随笔有详细的讲高阶函数,还有例子。
4.applyMiddleware方法的实现
先看我们上边对enhancer的调用,enhancer也就是我们的applyMiddleware接受了createStore做参数,返回了一个函数,这个函数的参数是reducer。现在我们对这种两层嵌套的函数已经不陌生了,其实它就是一个return两层的函数。
我们的applyMiddleware主要做了什么呢?首先通过传入的createStore方法create了一个store,然后将store的dispatch传递给middleware,由middleware对dispatch进行包装,返回一个带有被包装的dispatch的store。
看到这里,很简单嘛。但是注意,还记得我们是怎么使用异步的action的吗?
export function addAsync(){
return (dispatch, getState) => {
setTimeout(() => {
dispatch(add())
}, 2000);
}
}
居然还可以在可以在异步的action中拿到dispatch和getState方法,所以要对这个进行处理,也不是很难,把他俩传给我们的middle就好了。
都说到这里了,能不能自己写出来呢?
export function applyMiddleware (middleware){
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = store.dispatch
const midApi = {
getState: store.getState,
dispatch: (...args)=>dispatch(...args)
}
dispatch = middleware(midApi)(store.dispatch)
return {
...store,
dispatch
}
}
}
如果我们执行了被包装后的dispatch,就相当于执行了middleware(midApi)(store.dispatch)(action)
这段语句,这是一个三层的嵌套函数,我们也称作柯里化。
5.自己的redux-thunk
其实自己的thunk很简单,正常的action的的返回值是个对象,前面已经说过,异步的action的返回值是一个函数,那么我们只需要判断一下action的返回的类型即可。
const thunk = ({dispatch, getState}) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState)
}
return next(action)
}
export thunk
在这里呢,dispatch和getState就是我们在applyMiddleware中传入的那个midApi对象,next就是store.dispatch也可以理解为下一个中间件,如果action的类型是object,说明这是一个同步的,直接dispatch就好了,如果
action的类型是function,当触发这个dispatch的时候,就触发action这个函数,同时将dispatch和getState方法传入到action函数中,这也是为什么我们能在异步action中拿到dispatch和getState方法的原因。
6.多个中间件合并与compose方法
我们的applyMiddle方法还不是太完善,只能使用一个中间件,使用多个中间件怎么办,这个,简单,map一下呗。如果是要求多个中间件依此执行怎么办?还是map呀,好,来map一下。
我们会得到这样的代码:
const store = createStore(
reducer,
applyMiddleware(middlewareOne) (
middlewareTwo(
middlewareThree(
...
)
)
)
)
我们会发现,我们陷入了一个深度嵌套的函数当中,这时我们就需要一个compose方法来结合一下,方便我们的书写。
compose是函数式编程的一种写法,compose的作用是从右到左结合多个函数,形成一个最终函数。就是将fn1(fn2(fn3()))
的形式,变成compose(fn1, fn2, fn3)的形式。
compose 做的只是让你在写深度嵌套的函数时,避免了代码的向右偏移。不要觉得它很复杂。
compose方法的实现:
export function compose (...funcs){
if (funcs.length==0) {
return arg=>arg
}
if (funcs.length==1) {
return funcs[0]
}
return funcs.reduce((ret,item)=> (...args)=>{
console.log(ret)
return ret(item(...args))
})
}
compose不是那么复杂,关于如果想了解更多关于compose的知识,可以看看Redux对compose的说明
到这里我们可以使用多个中间件的applyMiddleware方法已经实现了,整个的applyMiddleware方法在这里:
export function applyMiddleware (...middlewares){
return createStore=>(...args)=>{
const store = createStore(...args)
let dispatch = store.dispatch
const midApi = {
getState:store.getState,
dispatch:(...args)=>dispatch(...args)
}
const middlewareChain = middlewares.map(middleware=>{
return middleware(midApi)
})
console.log(compose(...middlewareChain)(store.dispatch))
dispatch = compose(...middlewareChain)(store.dispatch)
return {
...store,
dispatch
}
}
}
export function compose(...funcs){
if (funcs.length==0) {
return arg=>arg
}
if (funcs.length==1) {
return funcs[0]
}
return funcs.reduce((ret,item)=> (...args)=>{
console.log(ret)
return ret(item(...args))
})
}
到这里,整个Redux和React-Redux的基本原理我们已经清楚了,也已经基本实现了,发现其中涉及到很多函数式编程和装饰者模式,还有一次观察者模式,所以,编程思想和设计模式是很重要的,有时间一定要加强这方面的学习。
我们现在有了这些基础,可以去看看Redux和React-Redux的源码,也大体上和我写的是差不多的,因为我也看了源码。
Redux和React-Redux的实现(三):中间件的原理和applyMiddleware、Thunk的实现的更多相关文章
- react+redux+react-redux练习项目
一,项目目录 二.1.新建pages包,在pages中新建TodoList.js: 2.新建store包,在store包中新建store.js,reducer.js,actionCreater.js, ...
- react+redux教程(七)自定义redux中间件
今天,我们要讲解的是自定义redux中间件这个知识点.本节内容非常抽象,特别是中间件的定义原理,那多层的函数嵌套和串联,需要极强逻辑思维能力才能完全消化吸收.不过我会多罗嗦几句,所以不用担心. 例子 ...
- react+redux教程(三)reduce()、filter()、map()、some()、every()、...展开属性
reduce().filter().map().some().every()....展开属性 这些概念属于es5.es6中的语法,跟react+redux并没有什么联系,我们直接在https:// ...
- react+redux教程(八)连接数据库的redux程序
前面所有的教程都是解读官方的示例代码,是时候我们自己写个连接数据库的redux程序了! 例子 这个例子代码,是我自己写的程序,一个非常简单的todo,但是包含了redux插件的用法,中间件的用法,连接 ...
- react+redux教程(五)异步、单一state树结构、componentWillReceiveProps
今天,我们要讲解的是异步.单一state树结构.componentWillReceiveProps这三个知识点. 例子 这个例子是官方的例子,主要是从Reddit中请求新闻列表来显示,可以切换reac ...
- react+redux教程(四)undo、devtools、router
上节课,我们介绍了一些es6的新语法:react+redux教程(三)reduce().filter().map().some().every()....展开属性 今天我们通过解读redux-undo ...
- react+redux教程(一)connect、applyMiddleware、thunk、webpackHotMiddleware
今天,我们通过解读官方示例代码(counter)的方式来学习react+redux. 例子 这个例子是官方的例子,计数器程序.前两个按钮是加减,第三个是如果当前数字是奇数则加一,第四个按钮是异步加一( ...
- 详解react/redux的服务端渲染:页面性能与SEO
亟待解决的疑问 为什么服务端渲染首屏渲染快?(对比客户端首屏渲染) react客户端渲染的一大痛点就是首屏渲染速度慢问题,因为react是一个单页面应用,大多数的资源需要在首次渲染前就加载 ...
- 实例讲解基于 React+Redux 的前端开发流程
原文地址:https://segmentfault.com/a/1190000005356568 前言:在当下的前端界,react 和 redux 发展得如火如荼,react 在 github 的 s ...
随机推荐
- 【转】打包2个10g文件 测试
微博上kevin_prajna提了一个问题:“求Linux下一打包工具,需求:能把两个10G的文件打包成一个文件,时间在1分钟之内能接受!”. 暂且作答一下吧.首先问题是求解工具,那么我们忽略IO问题 ...
- gulp插件 run-sequence(同步执行任务)
功能描述 gulp默认使用最大并发数执行任务,也就是说所有的任务几乎都是同时执行,而不会等待其它任务.但很多时候,任务是需要有先后次序的,比如要先清理目标目录,然后再执行打包. run-sequenc ...
- [图解tensorflow源码] Simple Placer节点布放算法
- C3P0与DBUtil配合实现DAO层的开发
写在前面:菜鸟拙见,望请纠正 一:为什么需要连接池 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,需要数据库连 ...
- summery 总结篇 访问对象属性的方法
访问一个对象的属性有两种方法: (1),通过“.”来访问:object.propertyName; (2),通过[]来访问:object[propertyName]; 访问一个对象的方法只能通过“.” ...
- 什么是控制反转(IOC)?什么是依赖注入?
控制反转是应用于软件工程领域中的,在运行时被装配器对象来绑定耦合对象的一种编程技巧,对象之间耦合关系在编译时通常是未知的.在传统编程方式中,业务逻辑的流程是应用程序中早已被设定好关联关系的对象来决定的 ...
- iview的Affix插件遇到滚动时候的bug处理方法
最近有个需求,是用vue做的页面,其中嵌入了一个tinymce编辑器,编辑器设置了自动调整高度,也就是说编辑器中内容越多,高度就会自动撑高 我们需要再页面最下方放一个保存按钮,保存按钮必须固定在屏幕下 ...
- 【FJOI2014】最短路径树问题
题面 题解 强行将最短路和点分治(长链剖分)融合在一起的题目 构建出字典序最小的最短路树之后,就可以用点分治来解决了 不过有一些细节要注意: 3 3 k 1 2 1 2 3 1 1 3 2 这样建出的 ...
- Codeforces 937 D. Sleepy Game(DFS 判断环)
题目链接: Sleepy Game 题意: Petya and Vasya 在玩移动旗子的游戏, 谁不能移动就输了. Vasya在订移动计划的时候睡着了, 然后Petya 就想趁着Vasya睡着的时候 ...
- Django—— restful 设计风格
RESTful Api设计风格 协议:API 与用户的通信协议,总是使用 HTTPS 协议 域名:应该尽量将 API 部署在专用域名之下,如果确定 API 很简单,不会有进一步的扩展,可以考虑放在主域 ...