redux中间件的原理_深入理解 Redux 中间件

时间: 2018-11-14阅读: 1249标签: redux

前言

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


一、Redux中间件机制

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

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

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

上面是很典型的一次 redux 的数据流的过程,但在增加了 middleware 后,我们就可以在这途中对 action 进行截获,并进行改变。且由于业务场景的多样性,单纯的修改 dispatch 和 reduce 人显然不能满足大家的需要,因此对 redux middleware 的设计是可以自由组合,自由插拔的插件机制。也正是由于这个机制,我们在使用 middleware 时,我们可以通过串联不同的 middleware 来满足日常的开发,每一个 middleware 都可以处理一个相对独立的业务需求且相互串联:

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

而不同的中间件之所以可以组合使用,是因为 Redux 要求所有的中间件必须提供统一的接口,每个中间件的尉氏县逻辑虽然不一样,但只要遵循统一的接口就能和redux以及其他的中间件对话了。


二、理解中间价的机制

由于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源码解读

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

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

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 函数的返回值。


三、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的变更。如下图所示:

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则要求较高,难度较大,我现在也并没有掌握和实践这种异步流的管理方式,因此较为底层的东西先就不讨论了。


参考资料:

  • 《深入浅出React和Redux》
  • 《深入React技术栈》
作者:srtian
链接:https://www.jianshu.com/p/ae7b5a2f78ae
來源:简书

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/1344

关闭

基于Hooks 的 Redux 速成课

你对 Redux 感到困惑吗?如果使用新的 Redux Hooks,会更加简单!这里是一个关于 Redux 的速成班,将配合 React 函数组件使用:

redux之redux-thunk和redux-saga

redux作为状态管理仓库,在我们前端应用中发挥着非常重要的作用,先放一张官方redux flow图片,使用middleWare背景:我们知道redux中数据流是同步的,不支持异步action更新或获取数据,但是在实际项目中异步请求数据绝对是高频出现,并且可以说占据了9成以上的业务场景

react关于 Redux与flux的比较学习

flux四大元素:Dispatcher:根据注册派发动作(action),Store: 存储数据,处理数据,Action:用于驱动Dispatcher,View: 用户界面视图。flux的目的:纠正MVC框架的无法禁绝view与model通信的缺点。Redux基本原则:继承Flux基本原则:单向数据流

如何使用useReducer Hook?

看到“reducer”这个词,容易让人联想到Redux,但是在本文中,不必先理解Redux才能阅读这篇文章。咱们将一起讨论“reducer”实际上是什么,以及如何利用useReducer来管理组件中的复杂状态,以及这个新钩子对Redux意味着什么?

如何使用24行JavaScript代码实现Redux?

Redux是迄今为止创建的最重要的JavaScript库之一,灵感来源于以前的艺术比如Flux和Elm,Redux通过引入一个包含三个简单要点的可伸缩体系结构,使得JavaScript函数式编程成为可能。如果你是初次接触Redux

动手实现一个react-redux

react-redux 是 redux 官方 React 绑定库。它帮助我们连接UI层和数据层。本文目的不是介绍 react-redux 的使用,而是要动手实现一个简易的 react-redux,希望能够对你有所帮助。

React Hooks 是不能替代 Redux 的

我的许多同事最近通过各种方式问同一类问题:“如果我们开始用 hook 后,那还有必要用 Redux 吗?”“React hook 不是让 Redux 过时了吗?那只用 Hooks 就可以做 Redux 所有能做的事了吧?”

带着问题看React-Redux源码

我在读React-Redux源码的过程中,很自然的要去网上找一些参考文章,但发现这些文章基本都没有讲的很透彻,很多时候就是平铺直叙把API挨个讲一下,而且只讲某一行代码是做什么的,却没有结合应用场景和用法解释清楚为什么这么做。

redux 和 mobx 调研结果- mobx

mobx 不同于 redux 的单一数据源的统一管理,它可以有多个 store, 为了便于维护 ,每一个 store 都是一个类,这样便于维护和扩展;mobx 不同于 redux 的单一数据源的统一管理

redux和react-redux

redux是react的状态管理工具,却不仅仅只是为了react而生的,所以在使用中会存在痛点。而react-redux是专门为了react定制,目的是为了解决redux的痛点,起到了补充的作用。flux无非就是一个常见的event dispatcher

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!