https://juejin.im/post/59e6cd68f265da43163c2821?utm_source=tuicool&utm_medium=referral
Redux 的核心理念是严格的单向数据流,只能通过 dispatch(action) 的方式修改 store,流程如下:
view -> action -> reducer -> store
而在实际业务中往往有大量异步场景,最原始的做法是在 React 组件 componentDidMount 的时候初始化异步流,通过 callback 或者 promise 的方式在调用 dispatch(action),这样做把 view 层和 model 层混杂在一起,耦合严重,后期维护非常困难。
之前的文章 解读 Redux 中间件的原理 可以知道,中间件(middleware)改写了 dispatch方法,因此可以更灵活的控制 dispatch 的时机,这对于处理异步场景非常有效。因此 Redux 作者也建议用中间件来处理异步流。社区常见的中间件有 redux-thunk、redux-promise、redux-saga、redux-observable 等。
redux-thunk:简单粗暴
作为 Redux 作者自己写的异步中间件,其原理非常简单:Redux 本身只会处理同步的简单对象 action,但可以通过 redux-thunk 拦截处理函数(function)类型的 action,通过回调来控制触发普通 action,从而达到异步的目的。其典型用法如下:
//constants 部分省略 //action creator const createFetchDataAction = function(id){ return function(dispatch, getState){ dispatch({ type: FETCH_DATA_START, payload: id }) api.fetchData(id) .then(response => { dispatch({ type: FETCH_DATA_SUCCESS, payload: response }) }) .catch(error => { dispatch({ type: FETCH_DATA_FAILED, payload: error }) }) } } //reducer const reducer = function(oldState, action){ switch(action.type) { case FETCH_DATA_START : // 处理 loading 等 case FETCH_DATA_SUCCESS : // 更新 store 等处理 case FETCH_DATA_FAILED : // 提示异常 } }
可以看到采用 redux-thunk 后,action creator 返回的 action 可以是个 function,这个 function 内部自己会在合适的时机 dispatch 合适的普通 action。而这里面也没有什么魔法,redux-thunk 其核心源码如下:
const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState); } return next(action); };
如果 action 是个 function,便将 dispatch 方法传入该函数并执行之。
redux-thunk 在使用时非常方便,能满足大部分场景,缺点就是样板代码太多,写起来费劲了点。
redux-promise:将 promise 贯彻到底
redux-thunk 是将从 api 返回的 promise resolve 后 dispatch 成不同 action,那直接将这个 promise 作为 action 给 dispatch,让中间件来处理 resolve 这个过程,岂不是就可以少写些 .then().catch() 之类的代码了吗?redux-promise 正是解决了这个问题。同样是从后端去数据,其典型用法为:
const FETCH_DATA = 'FETCH_DATA' //action creator const getData = function(id){ return { type: FETCH_DATA, payload: api.fetchData(id) // 直接将 promise 作为 payload } } //reducer const reducer = function(oldState, action){ switch(action.type) { case FETCH_DATA: if (action.status === 'success') { // 更新 store 等处理 } else { // 提示异常 } } }
这样下来比 redux-thunk 的写法瘦身不少。其核心源码与 redux-thunk 类似,如果 action或 action.payload 是 Promise 类型则将其 resolve,触发当前 action 的拷贝,并将 payload 设置为 promise 的 成功/失败结果。
export default function promiseMiddleware({ dispatch }){ return next => action => { if (!isFSA(action)) {// 判断是否是标准的 flux 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 的写法里 reducer 收到 action 时就已经被 resolve 了,这样如果要处理 loading 这种情景就还得写额外代码,而且在 action 这样一个简单对象里增加 status 属性会给人不规范的感觉,这可能就是步子迈大了容易扯到蛋吧。
redux-thunk 和 redux-promise 用法实际上比较类似,都是触发一个 function/promise 让中间件自己决定 dispatch 真正异步数据的时机,这对于大部分场景来说已经足够了。但是对于异步情况更复杂的场景,我们往往要写很多业务代码,一个异步结果返回后可能需要对应修改 store 里多个部分,这样就面临一个困惑的问题:业务代码是放在 action 层还是 reducer 里?例如,管理员冻结某用户的账户,需要同时更新 store 里 AllUserList 和 PendingUserlist, 这时候面临两种选择 :
点击按钮时触发一个 PEND_USER 的 action,然后在 reducer 对应 switch 里同时更新 AllUserList 和 PendingUserlist
点击按钮时触发 REFRESH_USER_LIST 和 REFRESH_PENDING_USER_LIST 两个 action,然后在 reducer 里分别更新两处 store。
一般来说用户一个动作触发一个 action 更符合常理,但是可能其他地方又有复用 REFRESH_USER_LIST 的地方,将 action 拆的更新更利于复用,这时候就得做个取舍了。
redux-saga:精准而优雅
而 redux-saga 就可以很好的解决这个问题,它在原来 Redux 数据流中增加了 saga 层(不要在意这个诡异的名字