如果没有中间件,store.dispatch只能接收一个普通对象作为action。在处理异步action时,我们需要在异步回调或者promise函数then内,async函数await之后dispatch。
1
2
3
4
5
6
7
8
9
|
dispatch({ type: 'before-load' }) fetch( 'http://myapi.com/${userId}' ).then({ response =>dispatch({ type: 'load' , payload:response }) }) |
这样做确实可以解决问题,特别是在小型项目中,高效,可读性强。 但缺点是需要在组件中写大量的异步逻辑代码,不能将异步过程(例如异步获取数据)与dispatch抽象出来进行复用。而采用类似redux-thunk之类的中间件可以使得dispatch能够接收不仅仅是普通对象作为action。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function load(userId){ return function (dispatch,getState){ dispatch({ type: 'before-load' }) fetch( 'http://myapi.com/${userId}' ).then({ response =>dispatch({ type: 'load' , payload:response }) }) } } //使用方式 dispatch(load(userId)) |
使用中间件可以让你采用自己方便的方式dispatch异步action,下面介绍常见的三种。
1. redux-thunk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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; |
2. redux-promise
使用redux-promise可以将action或者action的payload写成promise形式。 源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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 })) . catch (error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); }) : next(action); }; } |
3. Redux-saga
redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易
3.1 基本使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import { call, put, takeEvery, takeLatest} from 'redux-saga/effects' ; //复杂的异步流程操作 function * fetchUser(action){ try { const user = yield call(API.fetchUser, action.payload); yield put({type: "USER_FETCH_SUCCEEDED" ,user:user}) } catch (e){ yield put({type: "USER_FETCH_FAILED" ,message:e.message}) } } //监听dispatch,调用相应函数进行处理 function * mainSaga(){ yield takeEvery( "USER_FETCH_REQUESTED" ,fetchUser); } //在store中注入saga中间件 import {createStore,applyMiddleware} from 'redux' ; import createSagaMiddleware from 'redux-saga' ; import reducer from './reducers' ; import mainSaga from './mainSaga' ; const sagaMiddleware = createSagaMiddleware(); const store = createStore(reducer,initalState,applyMiddleware(sagaMiddleware)); sagaMiddleware.run(mainSaga) |
3.2 声明式effects,便于测试
为了测试方便,在generator中不立即执行异步调用,而是使用call、apply等effects创建一条描述函数调用的对象,saga中间件确保执行函数调用并在响应被resolve时恢复generator。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
function * fetchProducts() { const products = yield Api.fetch( '/products' ) dispatch({ type: 'PRODUCTS_RECEIVED' , products }) } //便于测试 function * fetchProducts() { const products = yield call(Api.fetch, '/products' ) //便于测试dispatch yield put({ type: 'PRODUCTS_RECEIVED' , products }) // ... } // Effect -> 调用 Api.fetch 函数并传递 `./products` 作为参数 { CALL: { fn: Api.fetch, args: [ './products' ] } } |
3.3 构建复杂的控制流
saga可以通过使用effect创建器、effect组合器、saga辅助函数来构建复杂的控制流。
effect创建器:
effect组合器:
race:阻塞性effect:用来命令 middleware 在多个 Effect 间运行 竞赛(Race)
1
2
3
4
5
6
|
function fetchUsersSaga { const { response, cancel } = yield race({ response: call(fetchUsers), cancel: take(CANCEL_FETCH) }) } |
all: 当 array 或 object 中有阻塞型 effect 的时候阻塞,用来命令 middleware 并行地运行多个 Effect,并等待它们全部完成
1
2
3
4
5
6
|
function * mySaga() { const [customers, products] = yield all([ call(fetchCustomers), call(fetchProducts) ]) } |
effect辅助函数:
takeEvery:非阻塞性effect,在发起(dispatch)到 Store 并且匹配 pattern 的每一个 action 上派生一个 saga
1
2
3
4
5
6
|
const takeEvery = (patternOrChannel, saga, ...args) => fork( function *() { while ( true ) { const action = yield take(patternOrChannel) yield fork(saga, ...args.concat(action)) } }) |
takeLatest:非阻塞性,在发起到 Store 并且匹配 pattern 的每一个 action 上派生一个 saga。并自动取消之前所有已经启动但仍在执行中的 saga 任务。
1
2
3
4
5
6
7
8
9
10
|
const takeLatest = (patternOrChannel, saga, ...args) => fork( function *() { let lastTask while ( true ) { const action = yield take(patternOrChannel) if (lastTask) { yield cancel(lastTask) // 如果任务已经结束,cancel 则是空操作 } lastTask = yield fork(saga, ...args.concat(action)) } }) |
throttle:非阻塞性,在 ms 毫秒内将暂停派生新的任务
1
2
3
4
5
6
7
8
9
|
const throttle = (ms, pattern, task, ...args) => fork( function *() { const throttleChannel = yield actionChannel(pattern) while ( true ) { const action = yield take(throttleChannel) yield fork(task, ...args, action) yield delay(ms) } }) |