jabez128 / jabez128.github.io

Programming and Thinking
104 stars 11 forks source link

redux中间件实战 #7

Open jabez128 opened 8 years ago

jabez128 commented 8 years ago

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又有以下几点不同:

  1. redux不存在一个中心dispatcher
  2. 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吧。

viweei commented 8 years ago

so good.

monkindey commented 8 years ago

函数式编程用的真棒,突然想到这个dispatch异步 redux-thunk中间件实现的原理是不是因为 调用dispatch方法,控制权从头开始,所以每次都会循环中间件呢?直到有数据才执行下一步next

xianyuxmu commented 7 years ago

@monkindey redux-thunk 应该是改造了 store.dispatch 方法,使得 store.dispatch 可以接受函数作为参数。接着,redux-thunk 使用了 Promise 来实现串行执行 Action。

monkindey commented 7 years ago

@xianyuxmu

redux-thunk 应该是改造了 store.dispatch 方法,使得 store.dispatch 可以接受函数作为参数

这个确实

redux-thunk 使用了 Promise 来实现串行执行 Action

redux-thunk 没用到promise吧

xianyuxmu commented 7 years ago

看了源代码了,确实不是使用 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;