文章首发于语雀 探究redux的副作用处理以及dispatch的promiseify 源码分析
未经允许严禁转载
我们知道redux的作用是管理应用程序的状态,让复杂应用能够更好得处理数据,使视图与数据一一对应。那redux的运行机制是什么样的呢?大致讲一下,就是用户行为产生action,dispatch接收action,然后通过reducer处理后生成新的state,最后更新store以及视图。看👇图
副作用
redux的设计思想就是不产生副作用,数据更改的状态可回溯,所以redux中处处都是纯函数。
那怎么处理副作用呢?redux没有提供解决直接的方案。但是它提供一个中间件机制,让用户去开发副作用处理的中间件。很多优秀的中间件也随着出现,如redux-thunk、redux-promise、redux-saga等。那它们是如何处理副作用的呢?请看👇
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最重要的思想,就是可以接受一个返回函数的 action creator
。如果这个 action creator
返回的是一个函数,就执行它,如果不是,就按照原来的 next(action)
执行。
正因为这个 action creator
可以返回一个函数,那么就可以在这个函数中执行一些异步的操作。
redux-promise
// 判断是不是Promise函数
import isPromise from 'is-promise';
// 标准flux action
import { isFSA } from 'flux-standard-action';
export default function promiseMiddleware({ dispatch }) {
return next => action => {
// 首先判断action是不是flux规定的类型
if (!isFSA(action)) {
return isPromise(action) ? action.then(dispatch) : next(action);
}
return isPromise(action.payload)
? action.payload
.then(result => dispatch({ ...action, payload: result }))
.catch(error => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
})
: next(action);
};
}
redux-promise将 promise
贯彻到底。将 promise
作为 action
传给 dispatch
,让中间件处理 resolve
,可以去少写 .then().catch()
之类的代码。
redux-thunk和redux-promise用法实际比较类似,都是触发一个 function/promise
让中间件自己决定处理副作用的时机。这能解决大部分的副作用场景,但对于更复杂的副作用情况,就需要写大量代码。而redux-saga正是能很好得解决这个问题。
redux-saga
用法和代码就不做探讨了。说说概念吧,redux-saga创造了一个saga层,专门用来处理副作用的。那么redux-saga是怎么处理副作用的呢?
首先,用户行为产生 action
,派发 reducer
时,saga层监听到特定的 action
(redux-saga提供一些辅助函数,用来监听将被派发到 reducer
的 action
)进行处理。处理 action
的函数叫做 Effect
,Effect
是个 Generate
函数,提供的 yield
可以用来控制代码执行,所以redux-saga适合处理异步。并且在 Effect
中也能继续发起一个普通 action
,让 reducer
处理。
这就是redux-saga的大致执行过程。好了,现在进入本文第二个主题,如何使 dispatch promiseify
。
dispatch promiseify
这里的 dispatch promiseify 指的是在使用redux和redux-saga情况下去实现的。那么开始吧。
目标实现
function* asyncIncrease(action) {
return action.payload
}
// 省略一些步骤
......
store.dispatch({
type: 'asyncIncrease', payload: 30
}).then(res => {
console.log(res); // 30
})
因为 Effect
是个 Generate
函数,那先来瞧瞧 Generate
的一些概念吧。
- 形式上,
Generator
函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态 - 调用
Generator
函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象 - 遇到
yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。
好了,暂时就这些概念,接下来看看怎么实现吧。
用户发起 action
到 reducer
后。因为 reducer
是个纯函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。所以我们得区分 action
,是由redux-saga处理还是reducer处理。所以得有个中间件,并且返回一个 promise
,使 dispatch
能够使用then
方法。
const promiseMiddlware = () => next => action => {
// 通过type来判断是不是由Effect处理
// 如果是,返回promise
if (isEffect(action.type) {
return new Promise((resolve, reject) => {
next({
resolve,
reject,
...action,
})
})
} else {
return next(action)
}
}
// action其实就包含了resolve,reject和payload
function* asyncIncreate(action) {
const { resolve, payload } = action
resolve(payload)
}
store.dispatch({
type: 'asyncIncrease', payload: 30
}).then(res => {
console.log(res); // 30
})
实现了 dispatch
的 promiseify
,但不觉得每次写个 Effect
,还需要写 resolve
,这样不麻烦吗?那我们转变一下思路,能不能再有个 Effect
专门来包装实际的 Effect
,外层 Effect
去 resolve
呢?
// 1. 先写个外层的Effect
function* baseEffect(action) {
const {resolve, reject,...rest} = action
// 2. 通过yield执行实际的Effect
const res = yield asyncIncreate(rest)
resolve(res)
}
// asyncIncreate就返回值就ok了
function* asyncIncreate(action) {
return action.payload
}
// 省略createStore等步骤
store.dispatch({
type: 'asyncIncrease', payload: 30
}).then(res => {
console.log(res); // 30
})
好了,dispatch
的 promiseify
就实现了,是不是挺简单的,这只是个粗略版本,还可以更灵活好用,需要深入了解的,可以去dva的源码。
总结
redux是目前最流行的react的状态管理库。由于redux的设计思想,导致副作用是个问题,但也不难解决,有优秀的中间件可以去解决。在使用redux和redux-saga时,如果想使 dispatch promiseify
呢,可以去写个中间件,再搭配一个外层 Effect
就ok了。