jabez128.github.io
jabez128.github.io copied to clipboard
redux中间件实战
tl;dr
2015年,随着react框架在前端开发领域的持续火热,单数据流向也开始逐渐被前端开发人员所认识和使用。单向数据流向指的是在整体web应用中,数据都是单向流动的。回想一下我们的校园时光,每当你没有生活费的时候,你会打电话找妈要钱,然后家里人去银行给你汇款,然后你去银行把钱取出来。在这个过程中,钱的流向是单向的:你 => 你妈 => 银行 => 你。这样的流程可以确保你不会去银行随意取钱挥霍,而且流程非常清晰。
同样的道理,web应用中数据的单向流动也是很有意义的。回想一下以前我们曾经干过的事情,我们是不是经常把状态存在视图中。比如下面的代码:
$(".button").click(function(){
if(this.hasClass("highlight")){
this.removeClass("highlight");
}else{
this.addClass("highlight");
}
})
除了把状态放在class里面,dom的dataset也是我们经常存放应用状态的地方。这样把状态和视图的耦合,对于编写读可维护的代码非常不利,尤其是在我们编写大型应用的时候。
Facebook的工程师提出了flux数据架构,它是数据单向流动的一种实现方式,目前实现的库也很多,比如reflux和Fluxxor。redux库灵感来源于flux,但和flux又有以下几点不同:
- redux不存在一个中心dispatcher
- redux中只有唯一一个store
redux代码简单轻巧,而且还有一个非常重要的特性:中间件。
redux中间件实战
本文假设你已经对redux有一些了解,如果你是一个redux新手,请先看redux中文文档。
redux代码中使用了很多函数式编程技巧,redux中间件也是一个高阶函数。我们用最简单的redux-thunk为例子:
function thunkMiddleware({ dispatch, getState }) {
return next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
}
首先我们需要记住的第一点是:
redux中间件是有格式的,也就是所谓的signature
({dispatch, store}) => (next) => (action) => {}
假设我们现在编写了两个最简单的中间件:
import {createStore, applyMiddleware, combineReducers} from "redux"
let reducer = (store={},action)=>{
return store
}
let logger1 = ({dispatch, getState}) => (next) => (action) => {
console.log("第一个logger开始");
next(action);
}
let logger2 = ({dispatch, getState}) => (next) => (action) => {
console.log("第二个logger开始");
next(action);
}
let store = createStore(reducer,applyMiddleware(logger1,logger2));
store.dispatch({
type: "type1",
})
运行代码结果,我们可以看到控制台输出了:
第一个logger开始
第二个logger开始
我们要记住的第二点:
中间件是对初始dispatch方法的包装
在上面的例子中,我们使用applyMiddleware对初始的dispatch方法进行了包装,因此我们在调用dispatch方法的时候,控制台有相应的输出。
我们要记住的第三点:
包装后的中间件从左往右进行包装,并从左往右执行
redux源码中使用了compose函数对中间件进行包装,使用applyMiddleware包装dispatch以后,新的dispatch方法可以可以理解为下面的方法:
let new_dispatch = (...args) = > logger1(logger2(dispatch(...args)))
我们要记住的第四点:
在中间件中调用next方法,控制权到达下一个中间件,调用dispatch方法,控制权从头开始
假设把logger2修改为下面的代码:
let logger2 = ({dispatch, getState}) => (next) => (action) => {
console.log("第一个logger开始");
dispatch(action);
}
那么控制台将持续输出"第一个logger开始 第二个logger开始",并最终程序爆栈终止。这是因为调用dispatch使控制权反复回到了第一个中间件。而next的调用方法,作用和express中间件的next相同。
我们要记住的最后一点:
中间件的return只会被上一级捕获。
let logger1 = ({dispatch, getState}) => (next) => (action) => {
console.log("第一个logger开始");
console.log(next(action));
return 123;
}
let logger2 = ({dispatch, getState}) => (next) => (action) => {
console.log("第二个logger开始");
next(action);
return 456;
}
let store = createStore(reducer,applyMiddleware(logger1,logger2));
console.log(store.dispatch({
type: "type1",
}));
logger1中的return能被最外层的console.log捕获,而logger2的return只会被logger1捕获。
结论
开发redux中间件只需要记住上面五点,一起来玩redux吧。
so good.
函数式编程用的真棒,突然想到这个dispatch异步 redux-thunk中间件实现的原理是不是因为
调用dispatch方法,控制权从头开始
,所以每次都会循环中间件呢?直到有数据才执行下一步next
@monkindey redux-thunk
应该是改造了 store.dispatch
方法,使得 store.dispatch
可以接受函数作为参数。接着,redux-thunk
使用了 Promise
来实现串行执行 Action。
@xianyuxmu
redux-thunk 应该是改造了 store.dispatch 方法,使得 store.dispatch 可以接受函数作为参数
这个确实
redux-thunk 使用了 Promise 来实现串行执行 Action
redux-thunk 没用到promise吧
看了源代码了,确实不是使用 Promise:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
}; }
const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware;
export default thunk;