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
层(不要在意这个诡异的名字