深刻理解 Redux 中间件

前言

最近几天对 redux 的中间件进行了一番梳理,又看了 redux-saga 的文档,和 redux-thunk 和 redux-promise 的源码,结合前段时间看的redux的源码的一些思考,感受对 redux 中间件的有了更加深入的认识,所以总结一下。react

1、Redux中间件机制

Redux自己就提供了很是强大的数据流管理功能,但这并非它惟一的强大之处,它还提供了利用中间件来扩展自身功能,以知足用户的开发需求。首先咱们来看中间件的定义:git

It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.json

这是Dan Abramov 对 middleware 的描述。简单来说,Redux middleware 提供了一个分类处理 action 的机会。在 middleware 中,咱们能够检阅每个流过的 action,并挑选出特定类型的 action 进行相应操做,以此来改变 action。这样提及来可能会有点抽象,咱们直接来看图,这是在没有中间件状况下的 redux 的数据流:redux

输入图片说明
redux.png

上面是很典型的一次 redux 的数据流的过程,但在增长了 middleware 后,咱们就能够在这途中对 action 进行截获,并进行改变。且因为业务场景的多样性,单纯的修改 dispatch 和 reduce 显然不能知足你们的须要,所以对 redux middleware 的设计理念是能够自由组合,自由插拔的插件机制。也正是因为这个机制,咱们在使用 middleware 时,咱们能够经过串联不一样的 middleware 来知足平常的开发需求,每个 middleware 均可以处理一个相对独立的业务需求且相互串联:api

image

如上图所示,派发给 redux Store 的 action 对象,会被 Store 上的多个中间件依次处理,若是把 action 和当前的 state 交给 reducer 处理的过程看作默认存在的中间件,那么其实全部的对 action 的处理均可以有中间件组成的。值得注意的是这些中间件会按照指定的顺序依次处理传入的 action,只有排在前面的中间件完成任务后,后面的中间件才有机会继续处理 action,一样的,每一个中间件都有本身的“熔断”处理,当它认为这个 action 不须要后面的中间件进行处理时,后面的中间件就不能再对这个 action 进行处理了。promise

而不一样的中间件之因此能够组合使用,是由于 Redux 要求全部的中间件必须提供统一的接口,每一个中间件的尉氏县逻辑虽然不同,但只要遵循统一的接口就能和redux以及其余的中间件对话了。app

2、理解中间价的机制

因为redux 提供了 applyMiddleware 方法来加载 middleware,所以咱们首先能够看一下 redux 中关于 applyMiddleware 的源码:异步

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    // 利用传入的createStore和reducer和建立一个store
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
      )
    }
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // 让每一个 middleware 带着 middlewareAPI 这个参数分别执行一遍
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 接着 compose 将 chain 中的全部匿名函数,组装成一个新的函数,即新的 dispatch
    dispatch = compose(...chain)(store.dispatch)
    return {
      ...store,
      dispatch
    }
  }
}
复制代码

咱们能够看到applyMiddleware的源码很是简单,但却很是精彩,具体的解读能够看个人这篇文章: redux源码解读async

从上面的代码咱们不难看出,applyMiddleware 这个函数的核心就在于在于组合 compose,经过将不一样的 middlewares 一层一层包裹到原生的 dispatch 之上,而后对 middleware 的设计采用柯里化的方式,以便于compose ,从而能够动态产生 next 方法以及保持 store 的一致性。ide

提及来可能有点绕,直接来看一个啥都不干的中间件是如何实现的:

const doNothingMidddleware = (dispatch, getState) => next => action => next(action)
复制代码

上面这个函数接受一个对象做为参数,对象的参数上有两个字段 dispatch 和 getState,分别表明着 Redux Store 上的两个同名函数,但须要注意的是并非全部的中间件都会用到这两个函数。而后 doNothingMidddleware 返回的函数接受一个 next 类型的参数,这个 next 是一个函数,若是调用了它,就表明着这个中间件完成了本身的职能,并将对 action 控制权交予下一个中间件。但须要注意的是,这个函数还不是处理 action 对象的函数,它所返回的那个以 action 为参数的函数才是。最后以 action 为参数的函数对传入的 action 对象进行处理,在这个地方能够进行操做,好比:

  • 调动dispatch派发一个新 action 对象
  • 调用 getState 得到当前 Redux Store 上的状态
  • 调用 next 告诉 Redux 当前中间件工做完毕,让 Redux 调用下一个中间件
  • 访问 action 对象 action 上的全部数据

在具备上面这些功能后,一个中间件就足够获取 Store 上的全部信息,也具备足够能力可用之数据的流转。看完上面这个最简单的中间件,下面咱们来看一下 redux 中间件内,最出名的中间件 redux-thunk 的实现:

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;
复制代码

redux-thunk的代码很简单,它经过函数是变成的思想来设计的,它让每一个函数的功能都尽量的小,而后经过函数的嵌套组合来实现复杂的功能,我上面写的那个最简单的中间件也是如此(固然,那是个瓜皮中间件)。redux-thunk 中间件的功能也很简单。首先检查参数 action 的类型,若是是函数的话,就执行这个 action 函数,并把 dispatch, getState, extraArgument 做为参数传递进去,不然就调用 next 让下一个中间件继续处理 action 。

须要注意的是,每一个中间件最里层处理 action 参数的函数返回值都会影响 Store 上的 dispatch 函数的返回值,但每一个中间件中这个函数返回值可能都不同。就好比上面这个 react-thunk 中间件,返回的多是一个 action 函数,也有可能返回的是下一个中间件返回的结果。所以,dispatch 函数调用的返回结果一般是不可控的,咱们最好不要依赖于 dispatch 函数的返回值。

3、redux的异步流

在多种中间件中,处理 redux 异步事件的中间件,绝对占有举足轻重的地位。从简单的 react-thunk 到 redux-promise 再到 redux-saga等等,都表明这各自解决redux异步流管理问题的方案

3.1 redux-thunk

前面咱们已经对redux-thunk进行了讨论,它经过多参数的 currying 以实现对函数的惰性求值,从而将同步的 action 转为异步的 action。在理解了redux-thunk后,咱们在实现数据请求时,action就能够这么写了:

function getWeather(url, params) {
    return (dispatch, getState) => {
        fetch(url, params)
            .then(result => {
                dispatch({
                    type: 'GET_WEATHER_SUCCESS', payload: result,
                });
            })
            .catch(err => {
                dispatch({
                    type: 'GET_WEATHER_ERROR', error: err,
                });
            });
        };
}
复制代码

尽管redux-thunk很简单,并且也很实用,但人老是有追求的,都追求着使用更加优雅的方法来实现redux异步流的控制,这就有了redux-promise。

3.2 redux-promise

不一样的中间件都有着本身的适用场景,react-thunk 比较适合于简单的API请求的场景,而 Promise 则更适合于输入输出操做,比较fetch函数返回的结果就是一个Promise对象,下面就让咱们来看下最简单的 Promise 对象是怎么实现的:

import { isFSA } from 'flux-standard-action';

function isPromise(val) {
  return val && typeof val.then === 'function';
}

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    if (!isFSA(action)) {
      return isPromise(action)
        ? action.then(dispatch)
        : next(action);
    }

    return isPromise(action.payload)
      ? action.payload.then(
          result => dispatch({ ...action, payload: result }),
          error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          }
        )
      : next(action);
  };
}
复制代码

它的逻辑也很简单主要是下面两部分:

  1. 先判断是否是标准的 flux action。若是不是,那么判断是不是 promise, 是的话就执行 action.then(dispatch),不然执行 next(action)。
  2. 若是是, 就先判断 payload 是不是 promise,若是是的话 payload.then 获取数据,而后把数据做为 payload 从新 dispatch({ ...action, payload: result}) ;不是的话就执行 next(action)

结合 redux-promise 咱们就能够利用 es7 的 async 和 await 语法,来简化异步操做了,好比这样:

const fetchData = (url, params) => fetch(url, params)
async function getWeather(url, params) {
    const result = await fetchData(url, params)
    if (result.error) {
        return {
            type: 'GET_WEATHER_ERROR', error: result.error,
        }
    }
        return {
            type: 'GET_WEATHER_SUCCESS', payload: result,
        }
    }
复制代码

3.3 redux-saga

redux-saga是一个管理redux应用异步操做的中间件,用于代替 redux-thunk 的。它经过建立 Sagas 将全部异步操做逻辑存放在一个地方进行集中处理,以此将react中的同步操做与异步操做区分开来,以便于后期的管理与维护。对于Saga,咱们可简单定义以下:

Saga = Worker + Watcher

redux-saga至关于在Redux原有数据流中多了一层,经过对Action进行监听,从而捕获到监听的Action,而后能够派生一个新的任务对state进行维护(这个看项目自己的需求),经过更改的state驱动View的变动。以下图所示:

image

saga特色:

  1. saga 的应用场景是复杂异步。
  2. 可使用 takeEvery 打印 logger(logger大法好),便于测试。
  3. 提供 takeLatest/takeEvery/throttle 方法,能够便利的实现对事件的仅关注最近实践仍是关注每一次实践的时间限频。
  4. 提供 cancel/delay 方法,能够便利的取消或延迟异步请求。
  5. 提供 race(effects),[...effects] 方法来支持竞态和并行场景。
  6. 提供 channel 机制支持外部事件。
function *getCurrCity(ip) {
    const data = yield call('/api/getCurrCity.json', { ip })
    yield put({
        type: 'GET_CITY_SUCCESS', payload: data,
    })
}
function * getWeather(cityId) {
    const data = yield call('/api/getWeatherInfo.json', { cityId })
    yield put({
        type: 'GET_WEATHER_SUCCESS', payload: data,
    })
}
function loadInitData(ip) {
    yield getCurrCity(ip)
    yield getWeather(getCityIdWithState(state))
    yield put({
        type: 'GET_DATA_SUCCESS',
    })
}
复制代码

总的来说Redux Saga适用于对事件操做有细粒度需求的场景,同时它也提供了更好的可测试性,与可维护性,比较适合对异步处理要求高的大型项目,而小而简单的项目彻底可使用 redux-thunk 就足以知足自身需求了。毕竟 react-thunk 对于一个项目自己而言,毫无侵入,使用极其简单,只需引入这个中间件就好了。而 react-saga 则要求较高,难度较大,但胜在优雅(虽然我以为asycn await的写法更优雅)。我如今也并无掌握和实践这种异步流的管理方式,所以较为底层的东西先就不讨论了。

参考资料:

  • 《深刻浅出React和Redux》
  • 《深刻React技术栈》
相关文章
相关标签/搜索