Redux:Middleware你咋就这么难

  这段时刻都在进修Redux,觉得对我来讲初学难度很大,中文官方文档读了好多遍才也许有点入门的觉得,小小地总结一下,起首能够看一下Redux的基础流程:
《Redux:Middleware你咋就这么难》

  从上面的图能够看出,简朴来讲,单一的state是存储在store中,当要对state举行更新的时刻,起首要提议一个action(经由历程dispatch函数),action的作用就是相当于一个音讯关照,用来形貌发作了什么(比方:增添一个Todo),然后reducer会依据action来举行对state更新,如许就能够依据新的state去衬着View。
  
  固然上面仅仅是发作同步Action的状况下,假如是Action是异步的(比方从服务器猎取数据),那末状况就有所不同了,必须要借助Redux的中间件Middleware。
  

Redux moddleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer

  依据官方的诠释,Redux中间件在提议一个actionaction抵达reducer的之间,供应了一个第三方的扩大。本质上经由历程插件的情势,将底本的action->redux的流程转变成action->middleware1->middleware2-> … ->reducer,经由历程转变数据流,从而完成比方异步Action、日记输入的功用。
  起首我们举例不运用中间件Middleware建立store:

import rootReducer from './reducers'
import {createStore} from 'redux'

let store =  createStore(rootReducer);

  那末运用中间件的状况下(以redux-logger举例),建立历程以下:

import rootReducer from './reducers'
import {createStore,applyMiddleware} from 'redux'
import createLogger from 'redux-logger'

const loggerMiddleware = createLogger();
let store = applyMiddleware(loggerMiddleware)(createStore)(rootReducer);

  
  那末我们不经要问了,为何采用了上面的代码就能够完成打印日记的中间件呢?
  起首给出applyMiddleware的源码(Redux1.0.1版本):

export default function applyMiddleware(...middlewares) {            return (next)  => 
        (reducer, initialState) => {

              var store = next(reducer, initialState);
              var dispatch = store.dispatch;
              var chain = [];

              var middlewareAPI = {
                getState: store.getState,
                dispatch: (action) => dispatch(action)
              };

              chain = middlewares.map(middleware =>
                            middleware(middlewareAPI));
              dispatch = compose(...chain, store.dispatch);
              return {
                ...store,
                dispatch
              };
           };
}

  上面的代码虽然只要不到20行,但看懂确实是不太轻易,现实上包括了函数式编程种种手艺,起首最显著的运用到了柯里化(Currying),在我明白中柯里化(Currying)现实就是将多参数函数转化为单参数函数并耽误实行函数,比方:

function add(x){
    return function(y){
        return x + y;
    }
}
var add5 = add(5);
console.log(add5(10)); // 10

  关于柯里化(Currying)更细致的引见能够看我之前的一篇文章从一道面试题谈谈函数柯里化(Currying)
  起首我们看applyMiddleware的整体构造:

export default function applyMiddleware(...middlewares) {            return (next)  => 
        (reducer, initialState) => {
        };
}

  哈哈,典范的柯里化(Currying),个中(...middlewares)用到了ES6中的新特征,用于将恣意个中间件参数转化为中间件数组,因而很轻易看出来在该函数的挪用要领就是:

let store = applyMiddleware(middleware1,middleware2)(createStore)(rootReducer);

  个中applyMiddleware形参和实参的对应关联是:

形参实参
middlewares[middleware1,middleware2]
createStoreRedux原生createStore
reducer, preloadedState, enhancer原生createStore须要填入的参数

  再看函数体:

var store = next(reducer, initialState);
var dispatch = store.dispatch;
var chain = [];
var middlewareAPI = {
    getState: store.getState,
    dispatch: (action) => dispatch(action)
};

  上面代码异常简朴,起首取得store,并将之前的store.dispatch存储在变量dispatch中,声明chain,以及将middleware须要的参数存储到变量middlewareAPI中。接下来的函数就有点难度了,让我们一行一行来看。

chain = middlewares.map(middleware => middleware(middlewareAPI))

  上面现实的寄义就是将middleware数组每个middleware实行
middleware(middlewareAPI)的返回值保留的chain数组中。那末我们不经要问了,中间件函数究竟是怎样的?我们供应一个精简版的createLogger函数:

export default function createLogger({ getState }) {
      return (next) => 
        (action) => {
              const console = window.console;
              const prevState = getState();
              const returnValue = next(action);
              const nextState = getState();
              const actionType = String(action.type);
              const message = `action ${actionType}`;

              console.log(`%c prev state`, `color: #9E9E9E`, prevState);
              console.log(`%c action`, `color: #03A9F4`, action);
              console.log(`%c next state`, `color: #4CAF50`, nextState);
              return returnValue;
    };
}

  可见中间件createLogger也是典范的柯里化(Currying)函数。{getState}运用了ES6的解构赋值,createLogger(middlewareAPI))返回的(也就是数组chain存储的是)函数的构造是:

(next) => (action) => {
//包括getState、dispatch函数的闭包
};

  我们接着看我们的applyMiddleware函数

dispatch = compose(...chain,store.dispatch)

  这句是最精巧也是最有难度的处所,注重一下,这里的...操纵符是数组睁开,下面我们先给出Redux中复合函数compose函数的完成(Redux1.0.1版本):

export default function compose(...funcs) {
     return funcs.reduceRight((composed, f) => f(composed));
}

  起首先明白一下reduceRight(我用过的次数戋戋可数,所以引见一下reducereduceRight)
  

Array.prototype.reduce.reduce(callback, [initialValue])

reduce要领有两个参数,第一个参数是一个callback,用于针对数组项的操纵;第二个参数则是传入的初始值,这个初始值用于单个数组项的操纵。须要注重的是,reduce要领返回值并非数组,而是形如初始值的经由叠加处置惩罚后的操纵。
callback离别有四个参数:

  1. accumulator:上一次callback返回的积累值

  2. currentValue: 当前值

  3. currentIndex: 当前值索引

  4. array: 数组
    比方:

var sum = [0, 1, 2, 3].reduce(function(a, b) {
return a + b;
}, 0);
// sum is 6

  reducereduceRight的区分就是从左到右和从右到左的区分。所以假如我们挪用compose([func1,func2],store.dispatch)的话,现实返回的函数是:

//也就是当前dispatch的值
func1(func2(store.dispatch))

  胜利在望,看末了一句:

return {
    ...store,
    dispatch
};

  这里现实上是ES7的用法,相当于ES6中的:

return Object.assign({},store,{dispatch:dispatch});

  或者是Underscore.js中的:

return _.extends({}, store, { dispatch: dispatch });

  懂了吧,就是新建立的一个对象,将store中的一切可罗列属性复制进去(浅复制),并用当前的dispatch掩盖store中的dispatch属性。所以

let store = applyMiddleware(loggerMiddleware)(createStore)(rootReducer);

中的store中的dispatch属性已不是之前的Redux原生的dispatch而是相似于func1(func2(store.dispatch))这类情势的函数了,然则我们不禁又要问了,那末中间件Midddleware又是怎样做的呢,我们看一下之前我们供应的发起的打印日记的函数:

export default function createLogger({ getState }) {
      return (next) => 
        (action) => {
              const console = window.console;
              const prevState = getState();
              const returnValue = next(action);
              const nextState = getState();
              const actionType = String(action.type);
              const message = `action ${actionType}`;

              console.log(`%c prev state`, `color: #9E9E9E`, prevState);
              console.log(`%c action`, `color: #03A9F4`, action);
              console.log(`%c next state`, `color: #4CAF50`, nextState);
              return returnValue;
    };
}

  假定一下,我们如今运用两个中间件,createLoggercreateMiddleware,个中createMiddleware的函数为

export default function createMiddleware({ getState }) {
      return (next) => 
        (action) => {
        return next(action)
    };
}

挪用情势为:

let store = applyMiddleware(createLogger,createMiddleware)(createStore)(rootReducer);

假如挪用了store.dispatch(action),chain中的两个函数离别是
createLoggercreateMiddleware中的

(next) => (action) => {}

部份。我们权且定名一下chain中关于createLogger的函数叫做
func1,关于createMiddleware的函数叫做func2。那末如今挪用
store.dispatch(action),现实就挪用了(注重递次)

//这里的store.dispatch是原始Redux供应的dispatch函数
func1(func2(store.dispatch))(action)

  上面的函数人人注重之前实行序次,起首func2(store.dispatch再是func1(args)(action)。关于func1取得的next的实参是参数是:

(action)=>{
    //func2中的next是store.dispatch
    next(action);
}

  那末现实上func1(...)(action)实行的时刻,也就是

const console = window.console;
const prevState = getState();
const returnValue = next(action);
const nextState = getState();
const actionType = String(action.type);
const message = `action ${actionType}`;

console.log(`%c prev state`, `color: #9E9E9E`, prevState);
console.log(`%c action`, `color: #03A9F4`, action);
console.log(`%c next state`, `color: #4CAF50`, nextState);
return returnValue;

的时刻,getState挪用的闭包MiddlewareAPI中的Redux的getState函数,挪用next(action)的时刻,会回调createMiddleware函数,然后createMiddlewarenext函数会回调真正的store.dispatch(action)。因而我们能够看出来现实的挪用递次是和传入中间件递次相反的,比方:

let store = applyMiddleware(Middleware1,Middleware2,Middleware3)(createStore)(rootReducer);

现实的实行是序次是store.dispatch->Middleware3->Middleware2->Middleware1
  不知道人人有无注重到一点,

var middlewareAPI = {
    getState: store.getState,
    dispatch: (action) => dispatch(action)
};

并没有直接运用dispatch:dispatch,而是运用了dispatch:(action) => dispatch(action),其目标是假如运用了dispatch:dispatch,那末在一切的Middleware中现实都援用的同一个dispatch(闭包),假如存在一个中间件修改了dispatch,就会致使背面一下一系列的题目,然则假如运用dispatch:(action) => dispatch(action)就能够防止这个题目。
  接下来我们看看异步的action怎样完成,我们先演示一个异步action creater函数:

export const FETCHING_DATA = 'FETCHING_DATA'; // 拉取状况
export const RECEIVE_USER_DATA = 'RECEIVE_USER_DATA'; //接收到拉取的状况
export function fetchingData(flag) {
    return {
        type: FETCHING_DATA,
        isFetchingData: flag
    };
}

export function receiveUserData(json) {
    return {
        type: RECEIVE_USER_DATA,
        profile: json
    }
}
export function fetchUserInfo(username) {
    return function (dispatch) {
        dispatch(fetchingData(true));
        return fetch(`https://api.github.com/users/${username}`)
            .then(response => {
                console.log(response);
                return response.json();
            })
            .then(json => {
                console.log(json);
                return json;
            })
            .then((json) => {
                dispatch(receiveUserData(json))
            })
            .then(() => dispatch(fetchingData(false)));
    };
}

  上面的代码用来从Github API中拉取名为username的用户信息,可见起首fetchUserInfo函数会dispatch一个示意最先拉取的action,然后运用fetch函数接见Github的API,并返回一个Promise,比及猎取到数据的时刻,dispatch一个收到数据的action,末了dispatch一个拉取完毕的action。由于一般的action都是一个纯JavaScript Object对象,然则异步的Action却返回的是一个function,这是我们就要运用的一个中间件:redux-thunk。
  我们给出一个相似redux-thunk的完成:

export default function thunkMiddleware({ dispatch, getState }) {
      return next => 
             action => 
                   typeof action === ‘function’ ? 
                     action(dispatch, getState) : 
                     next(action);
}

这个和你之前看到的中间件很相似。假如取得的action是个函数,就用dispatch和getState看成参数来挪用它,不然就直接分派给store。从而完成异步的Action。
  Redux入门进修,假如有写的不对的处所,愿望人人斧正,迎接人人围观我的博客:
  
  MrErHu
  SegmentFault

    原文作者:请叫我王磊同学
    原文地址: https://segmentfault.com/a/1190000008322583
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞