精益 React 学习指南 (Lean React)- 3.4 掌控 redux 异步
3.4 redux 异步
在大多数的前端业务场景中,需要和后端产生异步交互,在本节中,将详细讲解 redux 中的异步方案以及一些异步第三方组件,内容有:
3.4.1 redux 异步流前面讲的 redux 中的数据流都是同步的,流程如下:
3.4.2 实现异步的方式其实 redux 并未有和异步相关的概念,我们可以用任何原来实现异步的方式应用到 redux 数据流中,最简单的方式就是延迟 dispatch action,以 setTimeout 为例: this.dispatch({ type: 'SYNC_SOME_ACTION'}) window.setTimeout(() => { this.dispatch({ type: 'ASYNC_SOME_ACTION' }) },1000) 这种方式最简单直接,但是有如下问题:
解决上面两个问题的办法很简单,把异步的代码剥离出来: someAction.js function dispatchSomeAction(dispatch,payload) { // ..调用控制逻辑... dispatch({ type: 'SYNC_SOME_ACTION'}) window.setTimeout(() => { dispatch({ type: 'ASYNC_SOME_ACTION' }) },1000) } 然后组件只需要调用: import {dispatchSomeAction} from 'someAction.js' dispatchSomeAction(dispatch,payload); 基于这种方式上面的流程就改为了:
asyncActionDispatcher 和 actionCreator 是十分类似的,所以简单而言就可以把它理解为 asyncActionCreator,所以新的流程为:
但是上面的方法有一些缺点 同步调用和异步调用的方式不相同:
幸运的是在 redux 中通过 middleware 机制可以很容易的解决上面的问题 通过 middleware 实现异步我们已经很清楚一个 middleware 的结构 ,其核心的部分为 function(action) { // 调用后面的 middleware next(action) } middleware 完全掌控了 reducer 的触发时机, 也就是 action 到了这里完全由中间件控制,不乐意就不给其他中间件处理的机会,而且还可以控制调用其他中间件的时机。 举例来说一个异步的 ajax 请求场景,可以如下实现: function (action) { // async call fetch('....') .then( function resolver(ret) { newAction = createNewAction(ret,action) next(newAction) },function rejector(err) { rejectAction = createRejectAction(err,action) next(rejectAction) }) }); } 任何异步的 javascript 逻辑都可以,如: ajax callback,Promise,setTimeout 等等,也可以使用 es7 的 async 和 await。 第三方异步组件上面的实现方案只是针对具体的场景设计的,那如果是如何解决通用场景下的问题呢,其实目前已经有很多第三方 redux 组件支持异步 action,其中如:
这些组件都有很好的扩展性,完全能满足我们开发异步流程的场景,下面来一一介绍 3.4.3 redux-thunkredux-thunk 介绍redux-thunk 是 redux 官方文档中用到的异步组件,实质就是一个 redux 中间件,thunk 听起来是一个很陌生的词语,先来认识一下什么叫 thunk
简单来说一个 thunk 就是一个封装表达式的函数,封装的目的是延迟执行表达式 // 1 + 2 立即被计算 = 3 let x = 1 + 2; // 1 + 2 被封装在了 foo 函数内 // foo 可以被延迟执行 // foo 就是一个 thunk let foo = () => 1 + 2; redux-thunk 是一个通用的解决方案,其核心思想是让 action 可以变为一个 thunk ,这样的话:
我们已经知道了 thunk 本质上就是一个函数,函数的参数为 dispatch,所以一个简单的 thunk 异步代码就是如下: this.dispatch(function (dispatch){ setTimeout(() => { dispatch({type: 'THUNK_ACTION'}) },1000) }) 之前已经讲过,这样的设计会导致异步逻辑放在了组件中,解决办法为抽象出一个 asyncActionCreator,这里也一样,我们就叫 thunkActionCreator 吧,上面的例子可以改为: //actions/someThunkAction.js export function createThunkAction(payload) { return function(dispatch) { setTimeout(() => { dispatch({type: 'THUNK_ACTION',payload: payload}) },1000) } } // someComponent.js this.dispatch(createThunkAction(payload)) 安装和使用第一步:安装 $ npm install redux-thunk 第二步: 添加 thunk 中间件 import { createStore,applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers/index'; const store = createStore( rootReducer,applyMiddleware(thunk) ); 第三步:实现一个 thunkActionCreator //actions/someThunkAction.js export function createThunkAction(payload) { return function(dispatch) { setTimeout(() => { dispatch({type: 'THUNK_ACTION',1000) } } 第三步:组件中 dispatch thunk this.dispatch(createThunkAction(payload));
thunk 源码说了这么多,redux-thunk 是不是做了很多工作,实现起来很复杂,那我们来看看 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; 就这么简单,只有 14 行源码,但是这简短的实现却能完成复杂的异步处理,怎么做到的,我们来分析一下:
理解了这个过后是不是对 redux-thunk 的使用思路变得清晰了 thunk 的组合根据 redux-thunk 的特性,可以做出很有意思的事情
thunk 组合例子: function thunkC() { return function(dispatch) { dispatch(thunkB()) } } function thunkB() { return function (dispatch) { dispatch(thunkA()) } } function thunkA() { return function (dispatch) { dispatch({type: 'THUNK_ACTION'}) } } Promise 例子 function ajaxCall() { return fetch(...); } function thunkC() { return function(dispatch) { dispatch(thunkB(...)) .then( data => dispatch(thunkA(data)),err => dispatch(thunkA(err)) ) } } function thunkB() { return function (dispatch) { return ajaxCall(...) } } function thunkA() { return function (dispatch) { dispatch({type: 'THUNK_ACTION'}) } } 3.4.4 redux-promise另外一个 redux 文档中提到的异步组件为 redux-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 语法,简化异步的 promiseActionCreator 的设计, eg: export default async (payload) => { const result = await somePromise; return { type: "PROMISE_ACTION",payload: result.someValue; } } 如果对 es7 async 语法不是很熟悉可以看下面两个例子:
async function foo() { if(true) return 'Success!'; else throw 'Failure!'; } // 等价于 function foo() { if(true) return Promise.resolve('Success!'); else return Promise.reject('Failure!'); }
eg: async function foo(aPromise) { const a = await new Promise(function(resolve,reject) { // This is only an example to create asynchronism window.setTimeout( function() { resolve({a: 12}); },1000); }) console.log(a.a) return a.a } // in console > foo() > Promise {_c: Array[0],_a: undefined,_s: 0,_d: false,_v: undefined…} > 12 可以看到在控制台中,先返回了一个 promise,然后输出了 12 async 关键字可以极大的简化异步流程的设计,避免 callback 和 thennable 的调用,看起来和同步代码一致。 3.4.5 redux-sagaredux-saga 介绍redux-saga 也是解决 redux 异步 action 的一个中间件,不过和之前的设计有本质的不同
那到底什么是 sagaredux-saga 实际也没有解释什么叫 saga ,通过引用的参考:
这个定义的核心就是 CQRS-查询与责任分离 ,对应到 redux-sage 就是 action 与 处理函数的分离。 实际上在 redux-saga 中,一个 saga 就是一个 Generator 函数。 eg: import { takeEvery,takeLatest } from 'redux-saga' import { call,put } from 'redux-saga/effects' import Api from '...' /* * 一个 saga 就是一个 Generator Function * * 每当 store.dispatch `USER_FETCH_REQUESTED` action 的时候都会调用 fetchUser. */ function* mySaga() { yield* takeEvery("USER_FETCH_REQUESTED",fetchUser); } /** * worker saga: 真正处理 action 的 saga * * USER_FETCH_REQUESTED action 触发时被调用 * @param {[type]} action [description] * @yield {[type]} [description] */ function* fetchUser(action) { try { const user = yield call(Api.fetchUser,action.payload.userId); yield put({type: "USER_FETCH_SUCCEEDED",user: user}); } catch (e) { yield put({type: "USER_FETCH_FAILED",message: e.message}); } } 一些基本概念watcher saga 负责编排和派发任务的 saga worker saga 真正负责处理 action 的函数 saga helper 如上面例子中的 takeEvery,简单理解就是用于监控 action 并派发 action 到 worker saga 的辅助函数 Effect redux-saga 完全基于 Generator 构建,saga 逻辑的表达是通过 yield javascript 对象来实现,这些对象就是Effects。 这些对象相当于描述任务的规范化数据(任务如执行异步函数,dispatch action 到一个 store),这些数据被发送到 redux-saga 中间件中执行,如:
通过这种 effect 的抽象,可以避免 call 和 dispatch 的立即执行,而是描述要执行什么任务,这样的话就很容易对 saga 进行测试,saga 所做的事情就是将这些 effect 编排起来用于描述任务,真正的执行都会放在 middleware 中执行。 安装和使用第一步:安装 $ npm install --save redux-saga 第二步:添加 saga 中间件 import { createStore,applyMiddleware } from 'redux' import createSagaMiddleware from 'redux-saga' import reducer from './reducers' import mySaga from './sagas' // 创建 saga 中间件 const sagaMiddleware = createSagaMiddleware() // 添加到中间件中 const store = createStore( reducer,applyMiddleware(sagaMiddleware) ) // 立即运行 saga ,让监控器开始监控 sagaMiddleware.run(mySaga) 第三步:定义 sagas/index.js import { takeEvery } from 'redux-saga' import { put } from 'redux-saga/effects' export const delay = ms => new Promise(resolve => setTimeout(resolve,ms)) // 将异步执行 increment 任务 export function* incrementAsync() { yield delay(1000) yield put({ type: 'INCREMENT' }) } // 在每个 INCREMENT_ASYNC action 调用后,派生一个新的 incrementAsync 任务 export default function* watchIncrementAsync() { yield* takeEvery('INCREMENT_ASYNC',incrementAsync) } 第四步:组件中调用 this.dispatch({type: 'INCREMENT_ASYNC'}) redux-saga 基于 Generator 有很多高级的特性, 如:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |