Redux 的核心理念是严格的单向数据流,只能通过 dispatch(action) 的方式修改 store,流程如下:
view -> action -> reducer -> store
但是在业务复杂的以及和api数据对接的过程中肯定会遇到大量的异步操作。我们如何来解决这些场景呢?
redux中间件
什么是redux中间件
redux中间件
这里我们先从redux的中间件说起, 中间件,顾名思义:进行中间处理的物件。类似于面向对象编程的AOP编程思想(不了解AOP的可以忽略这句话)
简单的说:中间件可以控制在store dispatch action之前和之后的业务逻辑。也就是说 中间件实现了改写 store.dispatch 方法实现了action -> reducer的拦截的行为。
如果我们分别注册三个中间件: 中间件A 中间件B 中间件C
那么
中间件A -> 中间件B-> 中间件C-> 原始 dispatch -> 中间件C -> 中间件B -> 中间件A
和异步处理的关系
综上所述:中间件可以领过的改变 dispatch的时机,这样我们就可以很方便的处理异步场景了。
因此各种 redux异步处理中间件应运而生。比较知名的有redux-thunk和redux-saga。
redux-thunk
redux-thunk中间件可以让action创建函数先不返回一个action对象,而是返回一个函数,函数传递两个参数(dispatch,getState),在函数体内进行业务逻辑的封装
functionadd(){return{type:'ADD', }}functionaddIfOdd(){return(dispatch, getState) =>{constcurrentValue = getState();if(currentValue %2==0) {returnfalse; }//分发一个任务dispatch(add()) }}
详细代码可以查看分支:https://github.com/YahuiWong/react-native-typescript/tree/redux-thunk
redux-saga
sages 采用 Generator 函数来 yield Effects(包含指令的文本对象)。Generator 函数的作用是可以暂停执行,再次执行的时候从上次暂停的地方继续执行。Effect 是一个简单的对象,该对象包含了一些给 middleware 解释执行的信息。你可以通过使用 effects API 如 fork,call,take,put,cancel 等来创建 Effect。( redux-saga API 参考)
如 yield call(fetch, '/products') 即 yield 了下面的对象,call 创建了一条描述结果的信息,然后,redux-saga middleware 将确保执行这些指令并将指令的结果返回给 Generator:
// Effect -> 调用 fetch 函数并传递 `./products` 作为参数{type: CALL,function: fetch, args: ['./products']}
与 redux-thunk 不同的是,在 redux-saga 中,UI 组件自身从来不会触发任务,它们总是会 dispatch 一个 action 来通知在 UI 中哪些地方发生了改变,而不需要对 action 进行修改。redux-saga 将异步任务进行了集中处理,且方便测试。
dispacth({ type: 'FETCH_REQUEST', url: /* ... */} );
所有的东西都必须被封装在 sagas 中。sagas 包含3个部分,用于联合执行任务:
worker saga
做所有的工作,如调用 API,进行异步请求,并且获得返回结果
watcher saga
监听被 dispatch 的 actions,当接收到 action 或者知道其被触发时,调用 worker saga 执行任务
root saga
立即启动 sagas 的唯一入口
☀ 如何使用?
首先,我们得在文件入口中加入 saga 中间件,并且启动它,它会一直运行:
//...import{ createStore, applyMiddleware}from'redux';importcreateSagaMiddlewarefrom'redux-saga';importappReducerfrom'./reducers';importrootSagafrom'./saga';//...constsagaMiddleware = createSagaMiddleware()conststore=createStore(rootReducer,applyMiddleware(sagaMiddleware));sagaMiddleware.run(rootSaga)render(,document.getElementById('app'));
然后,就可以在 sagas 文件夹中集中写 saga 文件了:
import{delay}from'redux-saga';import{put,takeEvery,all}from'redux-saga/effects';import{ADD}from'./actionsTypes';function*addSync(){yielddelay(1000);yieldput({type:ADD})}function*watchaddSync(){yieldtakeEvery("addSync",addSync)}exportdefaultfunction*rootSaga(){yieldall([ watchaddSync() ])}
在 redux-saga 中的基本概念就是:sagas 自身不真正执行副作用(如函数 call),但是会构造一个需要执行作用的描述。中间件会执行该副作用并把结果返回给 generator 函数。
对上述例子的说明:
(1)引入的 redux-saga/effects 都是纯函数,每个函数构造一个特殊的对象,其中包含着中间件需要执行的指令,如:call(fetchUrl, url) 返回一个类似于 {type: CALL, function: fetchUrl, args: [url]} 的对象。
(2)在 watcher saga watchFetchRequests中:
首先 yield take('FETCH_REQUEST') 来告诉中间件我们正在等待一个类型为 FETCH_REQUEST 的 action,然后中间件会暂停执行 wacthFetchRequests generator 函数,直到 FETCH_REQUEST action 被 dispatch。一旦我们获得了匹配的 action,中间件就会恢复执行 generator 函数。
下一条指令 fork(fetchUrl, action.url) 告诉中间件去无阻塞调用一个新的 fetchUrl 任务,action.url 作为 fetchUrl 函数的参数传递。中间件会触发 fetchUrl generator 并且不会阻塞 watchFetchRequests。当fetchUrl 开始执行的时候,watchFetchRequests 会继续监听其它的 watchFetchRequests actions。当然,JavaScript 是单线程的,redux-saga 让事情看起来是同时进行的。
(3)在 worker saga fetchUrl 中,call(fetch,url) 指示中间件去调用 fetch 函数,同时,会阻塞fetchUrl 的执行,中间件会停止 generator 函数,直到 fetch 返回的 Promise 被 resolved(或 rejected),然后才恢复执行 generator 函数。
最后,总结一下 redux-saga 的优点:
(1)声明式 Effects:所有的操作以JavaScript对象的方式被 yield,并被 middleware 执行。使得在 saga 内部测试变得更加容易,可以通过简单地遍历 Generator 并在 yield 后的成功值上面做一个 deepEqual 测试。
(2)高级的异步控制流以及并发管理:可以使用简单的同步方式描述异步流,并通过 fork 实现并发任务。
(3)架构上的优势:将所有的异步流程控制都移入到了 sagas,UI 组件不用执行业务逻辑,只需 dispatch action 就行,增强组件复用性。
详细代码可以查看分支:https://github.com/YahuiWong/react-native-typescript/tree/redux-saga 如果觉得有用,请Star ,谢谢!
参考:
https://segmentfault.com/a/1190000007248878#articleHeader7