Redux系列之分析中间件原理(附经验分享)

前提:公司兼容了两种技术栈VueReactVue研究的比较多一些,反观React还停留在查官方文档阶段,最近恰好维护了一个React项目,项目中用到Redux,借此从新复习Reduxjavascript

Redux

一句话介绍Redux:Redux是一个可预测化的JavaScript状态管理容器。html

三大原则

理解Redux离不开这三大原则java

单一数据源

整个应用的 state 被储存在一棵 object tree 中,而且这个 object tree 只存在于惟一一个 store 中。git

State 是只读的

惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。github

store.dispatch({
  type'COMPLETE_TODO',
  index1
})
复制代码

使用纯函数来执行修改

为了描述 action 如何改变 state tree ,你须要编写 reducers。action是描述修改操做,而真正去操做修改state是reducersweb

function reducer(state = [], action{
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          completedfalse
        }
      ]
    case 'COMPLETE_TODO':
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completedtrue
          })
        }
        return todo
      })
    default:
      return state
  }
}
复制代码

经过上面👆的分析咱们获得三个关键的信息点:state,action,reducersredux

概念

在复习工做流以前咱们先来搞清楚几个概念,如下的概念解释都出自于词汇表 , 为了方便阅读,我就摘抄过来了promise

State

State (也称为 state tree) 是一个宽泛的概念,可是在 Redux API 中,一般是指一个惟一的 state 值,由 store 管理且由 getState() 方法得到。它表示了 Redux 应用的所有状态,一般为一个多层嵌套的对象。网络

约定俗成

顶层 state 或为一个对象,或像 Map 那样的键-值集合,也能够是任意的数据类型。然而你应尽量确保 state 能够被序列化,并且不要把什么数据都放进去,致使没法轻松地把 state 转换成 JSON。app

Action

Action 是一个普通对象,用来表示即将改变 state 的意图。它是将数据放入 store 的惟一途径。不管是从 UI 事件、网络回调,仍是其余诸如 WebSocket 之类的数据源所得到的数据,最终都会被 dispatch 成 action。

约定俗成

action 必须拥有一个 type 域,它指明了须要被执行的 action type。Type 能够被定义为常量,而后从其余 module 导入。比起用 Symbols 表示 type,使用 String 是更好的方法,由于 string 能够被序列化。

Reducer

Reducer (也称为 reducing function) 函数接受两个参数:以前累积运算的结果和当前被累积的值,返回的是一个新的累积结果。该函数把一个集合归并成一个单值。

在 Redux 中,累计运算的结果是 state 对象,而被累积的值是 action。Reducer 由上次累积的结果 state 与当前被累积的 action 计算获得一个新 state。这些 Reducer 必须是纯函数,并且当输入相同时返回的结果也会相同。它们不该该产生任何反作用。正因如此,才使得诸如热重载和时间旅行这些很棒的功能成为可能。

dispatch 函数

dispatching function(或简言之 dispatch function) 是一个接收 action 或者异步 action的函数,该函数要么往 store 分发一个或多个 action,要么不分发任何 action。

Action Creator

Action Creator 很简单,就是一个建立 action 的函数。不要混淆 action 和 action creator 这两个概念。Action 是一个信息的负载,而 action creator 是一个建立 action 的工厂

异步 Action

异步 action 是一个发给 dispatching 函数的值,可是这个值还不能被 reducer 消费。在发往 base dispatch() function 以前,middleware 会把异步 action 转换成一个或一组 action。异步 action 能够有多种 type这取决于你所使用的 middleware。它一般是 Promise 或者 thunk 之类的异步原生数据类型虽然不会当即把数据传递给 reducer,可是一旦操做完成就会触发 action 的分发事件

Middleware

Middleware 是一个组合 dispatch function 的高阶函数返回一个新的 dispatch function,一般将异步 actions 转换成 action。

❗️❗️这也是接下来咱们要重点分析

最后放出一张记忆脑图

Redux工做流

了解了三大原则以及概念之后,来看看Redux的工做流吧

对比下图就能轻松理解了

从图中咱们知道Redux是单向数据流,那么根据上面所学的知识咱们来设计下咱们的Redux目录结构

如上图,大部分公司的Redux目录结构应该相似这样,咱们须要actionCreators文件来建立咱们的actionaction对象必须拥有一个 type 域,而后reducer根据不一样的type触发对应的操做,因此建立actionTypes文件,接下来就是处理reducer了来修改咱们的state,因此建立reducer文件📃,因此明白上述概念,有助于咱们对目录结构的理解,而不是傻乎乎的跟着别人的目录结构照猫画虎的建立,起码要明白为何这样划分

compose聚合函数

再看中间件原理时,咱们来实现下compose函数,理解它对于理解咱们中间件原理有很大帮助

dispatch=fn1(fn2(fn3))
dispatch=compose(fn1,fn2,fn3)
复制代码

咱们期待有一个聚合的方法compose,能够这样使用,参数从右至左,将第一个参数fn3做为第二个参数fn2的参数,并将运行结果做为第三个参数fn1的参数,依次递推,最终返回一个新的函数,这个新函数在基础函数f3的基础上,获得了全部的高阶函数的能力,🤔思考:假设这个f3参数是换成是dispatch函数呢?,不着急,咱们接着往下分析compose函数的实现

// compose聚合函数的顺序是从右到左 from right to left
const compose = function (...funcs{
    return funcs.reduce((a, b) => {
        return (...args) => {
           return a(b(...args))
        }
    })
}
复制代码

这些串联函数不优雅。ES6 的箭头函数简写 ,从而看起来更舒服一些

 function compose(...funcs{
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
复制代码

因此compose(fn1, fn2, fn3) (...args) 至关于 fn1(fn2(fn3(...args)))

Redux中间件原理

讲清楚了上述👆基本内容后,到了本问的关键点了,那就是Redux中间件是用来干吗的,原理是什么❓

前面分析Redux工做流Action发出之后,Reducer 当即算出 State,是个同步流程,那么想一想如何支持异步操做,不仅仅支持异步操做,还要支持错误处理、日志监控,那么是在Redux工做流哪一个环节进行拦截操做呢❓,答案是dispatch过程,在分发action进行拦截处理

在Redux中,与中间件的实现相关联的方法是applyMiddleware,因此咱们来分析下这个方法吧(这里笔者提供一份调试中间件代码点击进入仓库

// 调用applyMiddleware
applyMiddleware(thunk, logger)

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) => {
        return dispatch(...args)
      }
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)
    return {
      ...store,
      dispatch
    }
  }
}
复制代码

能够看到dispatch = compose(...chain)(store.dispatch)这行关键代码,Redux为了支持中间件,内部重写了原来的dispatch方法,同时最后传入原来的store.dispatch方法,也就是将原来的disptach方法也当作中间件处理了

分析到这里,咱们能够知道传入的中间件compose函数聚合后改写dispatch方法,因此咱们也能够理解成中间件是对dispatch方法的加强,好比说:加强dispatch函数对action是函数、Promise的处理

举个例子🌰

仓库内修改为这段代码

function logger(store{
    return function wrapDispatchToAddLogging(next{
        return function dispatchAndLog(action{
            let result = next(action)
            return result
        }
    }
}

function thunk({ dispatch, getState }{
    return function wrapDispatchToThunk(next{
        return function dispatchThunk(action{
            if (typeof action === 'function') {
                return action(dispatch, getState);
            }
            return next(action);
        }
    }
}

applyMiddleware(thunk, logger)  // 至关于 wrapDispatchToThunk(wrapDispatchToAddLogging(dispatch))  
复制代码

打印dispatch方法

dispatchThunk(action) {
            if (typeof action === 'function') {
                return action(dispatch, getState);
            }
            return next(action);
    }
复制代码

转换next

// 转换next
dispatchThunk(action) {
            if (typeof action === 'function') {
                return action(dispatch, getState);
            }
            // next(action)
            // 这里的next由来是执行logger方法返回了dispatchAndLog函数
            return (function dispatchAndLog(action{
            let result = next(action)
            return result
        })(action)
   }   
复制代码

继续转换next

看到dispatchAndLog函数里还有个next,咱们继续转换

// 转换next
dispatchThunk(action) {
            if (typeof action === 'function') {
                return action(dispatch, getState);
            }
            // next(action)
            // 这里的next由来是执行logger方法返回了dispatchAndLog函数
            return (function dispatchAndLog(action{
            // let result = next(action)
           // 这里的dispatch是原来的dispacth
            let result=(function(action){dispatch(action)})(action)             
            return result
        })(action)
   }   
复制代码

看到这里咱们已经知道,Redux实现中间件的原理核心是加强原来dispatch函数的能力,然函数拥有某种能力天然而然想到高阶函数的处理方式,compose 方法将新的 middlewares 和 store.dispatch 结合起来,生成一个新的 dispatch 方法,另外经过改写后的dispatch方法,能够肯定Redux中间件也是基于洋葱模型

中间件的执行顺序

执行顺序听从洋葱模型

  applyMiddleware( 
    logger,
    thunk
  )
复制代码

如何写Redux中间件

工做中咱们明白了Redux工做流的状况下,其实干扰最多的可能就是Redux中间件了,经常使用的有redux-thunk、redux-soga、redux-promise等等,因此掌握中间件原理仍是很重要的,那么如何去写一个中间件❓,咱们经过继续分析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) => {
        return dispatch(...args)
      }
    }
    // 这里执行了一层中间件接收了{store.getState,dispatch}参数
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // compose(...chain)(store.dispatch) 至关于fn1(fn2(fn3(store.dispatch)))
    // 又执行了一层中间件 这一层接收next参数 也就是下一个中间件参数
    dispatch = compose(...chain)(store.dispatch)
    return {
      ...store,
      dispatch
    }
  }
}
复制代码

经过上面👆的分析可知,一个Redux中间件的基本形式(结构)以下

// 中间件逻辑代码须要通过三次柯里化
store => next => action => {
  // 中间件逻辑代码
}
复制代码

Redux支持异步操做

  • 强化dispacth函数,让其能解析action为函数形式,从而让Redux支持异步操做
  • 强化dispacth函数,让其能解析action为Promise对象形式,从而让Redux支持异步操做
redux-thunk

根据这个中间件结构咱们来分析redux-thunk中间件的源码

function createThunkMiddleware(extraArgument{
  return ({ dispatch, getState }) => (next) => (action) => {
    // 若是是函数 thunk来处理
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    // 其它处理不了,交给下一个中间件处理
    return next(action);
  };
}
const thunk = createThunkMiddleware();
复制代码

嗯嗯…,redux-thunk就这么几行源代码就实现了支持Redux异步操做

redux-promise

用法

  const testRes = () => {
      return new Promise((res, rej) => {
          res({
              type'TEST_RESOLVE'
          })
      });
  }
  store.dispatch(testRes());
复制代码
// redux-promise简易版源码
const vanillaPromise = store => next => action => {
  // 判断不是Promise对象,交给下个中间件处理
  if (typeof action.then !== 'function') {
    return next(action)
  }
  // action为Promise对象,promise中间件能作处理
  // 最后异步执行完触发执行store.dispatch ---> (...args) =>  dispatch(...args)

  return Promise.resolve(action).then(store.dispatch)
}
复制代码

改造咱们的Redux项目

一句话Redux用起来太笨重了,去年实习期开始写React项目时候,跟着别人的风格来写而已,直到维护同事的React项目,才猛然意识到项目中糟糕Redux写法,滥用Redux,致使了这个项目是灾难级别的

后面意识到能不能二次封装下Redux,简化写法

好比:

import { createModel } from "../../../model.js";

const model = {
  namespace'counter',
  state: {
    count10
  },
  reducer: {
    add(state: any, action: any) { // counter/add
      state.count += 1
    },
    minus(state: any, action: any) {
      state.count--
    },
  }
}

export default createModel(model)
复制代码

enennene….就有了以上对话,也感谢大佬的帮助,最后选用了remacth方案,这个库也挺好友好,兼容了老的写法,具体能够细看这个它的文档,这里就不作分析了

相关文章
相关标签/搜索