最近几天对 redux 的中间件进行了一番梳理,又看了 redux-saga 的文档,和 redux-thunk 和 redux-promise 的源码,结合前段时间看的redux的源码的一些思考,感受对 redux 中间件的有了更加深入的认识,所以总结一下。react
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 的数据流的过程,但在增长了 middleware 后,咱们就能够在这途中对 action 进行截获,并进行改变。且因为业务场景的多样性,单纯的修改 dispatch 和 reduce 显然不能知足你们的须要,所以对 redux middleware 的设计理念是能够自由组合,自由插拔的插件机制。也正是因为这个机制,咱们在使用 middleware 时,咱们能够经过串联不一样的 middleware 来知足平常的开发需求,每个 middleware 均可以处理一个相对独立的业务需求且相互串联:api
如上图所示,派发给 redux Store 的 action 对象,会被 Store 上的多个中间件依次处理,若是把 action 和当前的 state 交给 reducer 处理的过程看作默认存在的中间件,那么其实全部的对 action 的处理均可以有中间件组成的。值得注意的是这些中间件会按照指定的顺序依次处理传入的 action,只有排在前面的中间件完成任务后,后面的中间件才有机会继续处理 action,一样的,每一个中间件都有本身的“熔断”处理,当它认为这个 action 不须要后面的中间件进行处理时,后面的中间件就不能再对这个 action 进行处理了。promise
而不一样的中间件之因此能够组合使用,是由于 Redux 要求全部的中间件必须提供统一的接口,每一个中间件的尉氏县逻辑虽然不同,但只要遵循统一的接口就能和redux以及其余的中间件对话了。app
因为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 对象进行处理,在这个地方能够进行操做,好比:
在具备上面这些功能后,一个中间件就足够获取 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 异步事件的中间件,绝对占有举足轻重的地位。从简单的 react-thunk 到 redux-promise 再到 redux-saga等等,都表明这各自解决redux异步流管理问题的方案
前面咱们已经对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。
不一样的中间件都有着本身的适用场景,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);
};
}
复制代码
它的逻辑也很简单主要是下面两部分:
结合 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,
}
}
复制代码
redux-saga是一个管理redux应用异步操做的中间件,用于代替 redux-thunk 的。它经过建立 Sagas 将全部异步操做逻辑存放在一个地方进行集中处理,以此将react中的同步操做与异步操做区分开来,以便于后期的管理与维护。对于Saga,咱们可简单定义以下:
Saga = Worker + Watcher
redux-saga至关于在Redux原有数据流中多了一层,经过对Action进行监听,从而捕获到监听的Action,而后能够派生一个新的任务对state进行维护(这个看项目自己的需求),经过更改的state驱动View的变动。以下图所示:
saga特色:
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的写法更优雅)。我如今也并无掌握和实践这种异步流的管理方式,所以较为底层的东西先就不讨论了。
参考资料: