介绍redux-saga使用和经常使用api介绍的文章不少,可是真正介绍原理的却不多,下面我用本身的思路讲一下redux-saga的执行过程。源码有不少删减,感兴趣的可自行查看。html
redux-saga是用于管理 Side Effects(异步操做/反作用)的redux中间件react
redux中间件是经过改变store.dispatch使dispatch(action)时能处理反作用。在这里咱们用于处理异步操做。
具体可参考http://www.ruanyifeng.com/blo...。若是对中间件不熟悉,请必定把这篇文章看完再往下进行。ios
redux-saga主要是借鉴 sagas模式 和使用 generators 进行实现的。es6
首先,咱们讲讲sagas模式:解决长时间运行的事务致使的系统运行效率以及并发能力的问题,将业务分为多个独立的事务,每一个业务都会确保拥有修正事务(回滚),若是业务过程遇到了错误的状况而且没法继续,它就能够执行修正事务来修正已经完成的步骤,这样以保证最终的一致性。举个栗子:A事务须要等待B事务完成以后才能执行,若是B事务须要的时间很长,那么A事务就要等好久才能执行,若是用sagas模式,能够把A事务和B事务分别执行,若是B事务执行失败,则把A事务进行回滚。编程
sagas模式参考文档:https://blog.csdn.net/ethanwh...redux
redux-saga中对sagas的借鉴:在redux-saga里,一个saga就是一个生成器函数(generator function),能够在系统内无限期运行。当特定action被dispatch时,saga就能够被唤醒。saga也能够继续dispatch额外的actions,也能够接入程序的单一状态树。也能从主应用程序启动,暂停和取消。借鉴了sagas的知足特殊条件的长事务,可回滚。axios
接下来说讲generator
Generator 函数是 ES6 提供的一种异步编程解决方案。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,仍是一个遍历器对象生成函数。返回的遍历器对象,能够依次遍历 Generator 函数内部的每个状态。形式上,Generator 函数是一个普通函数,可是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不一样的内部状态(yield在英语里的意思就是“产出”)。为了方便,下文中gen是generator的简称。后端
举个简单的例子api
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();
执行结果数组
hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
再来一个复杂一点的传值的例子
function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var b = foo(5);
执行结果
b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }
具体可参考文档http://es6.ruanyifeng.com/#do...,这里很重要,请了解一下。
了解完上面的基础,下面咱们开始讲redux-saga的执行过程。
咱们以一个demo的形式进行分析。
须要解决几个问题:
当咱们点击按钮时,会从后端请求接口,将返回的数据更新到页面上
咱们先本身实现一个中间件解决这个需求:
import axios from 'axios'; const takers = []; // 存generator // 执行gen function runGen(dispatch, gen) { // 防止无限循环 if (!takers.length) { return; } // 遍历执行generator takers.forEach((taker, key) => { const g = taker(); // 删除已执行的taker takers.splice(key, 1); // 执行yield axios.post并返回一个promise const userPromise = g.next().value; userPromise.then((user) => { // 把{dispatch, user}设置为上一步的返回值,并执行yield dispatch g.next({ dispatch, user }); // 执行yield takers g.next() }); }) } export default function fetchMiddleware(_ref2) { var getState = _ref2.getState, dispatch = _ref2.dispatch; // 初始化时注册taker,把generator添加到takers,用于dispatch时执行 fetchMiddleware.run = () => takers.push(gen) // 改变dispatch return (next) => { return (action) => { // dispatch时执行这里 var result = next(action); runGen(dispatch, gen) return result; }; }; return fetchMiddleware; }
怎么用呢
import fetchMiddleware from './fetchMiddleware'; // 初始化 fetchMiddleware.run()
// 须要执行的gen function* gen(){ const { dispatch, user } = yield axios.post('http://rest.learncode.academy/api/wetern/users'); const { data } = user; yield dispatch({ type: 'UPDATE_USER', payload: data }) yield takers.push(gen) }
如今咱们来看看这个中间件实现步骤
点击按钮,执行dispatch({ type: 'FEATCH_USER })
对于上面那个例子,咱们用gen.next()实现一步一步执行。
这里咱们简单的实现了这个需求,redux-saga提供了更多更强大的api,下面咱们看看redux-saga是怎么实现的吧。
先来看咱们代码中如何使用redux-saga吧
./index.js import { put, call, take } from 'redux-saga/effects'; import { createStore, applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux-saga'; // 建立action const action = type => store.dispatch({ type }) // 建立redux-saga中间件 const sagaMiddleware = createSagaMiddleware() // 生成store const store = createStore(rootReducer, applyMiddleware(sagaMiddleware)) // 执行redux-saga中间件 sagaMiddleware.run(rootSaga) function render() { ReactDOM.render( <App users={store.getState().users} onFetchUser={() => action('FETCH_USER')} />, document.getElementById('root'), ) }
./saga.js // 建立saga export function* rootSaga() { while(true) { yield take('FETCH_USER'); const { data } = yield call(axios.post, 'http://rest.learncode.academy/api/wetern/users'); yield put({ type: 'UPDATE_USER', payload: data }) } }
function sagaMiddleware(_ref2) { var getState = _ref2.getState, dispatch = _ref2.dispatch; // next = store.dispatch 从redux中间件得出 return function (next) { return function (action) { // dispatch(action) dispatch的时候会到这一步 var result = next(action); // hit reducers // emitter 简单的事件对象,subscribe订阅/emit触发 sagaEmitter.emit(action); return result; }; }; }
// 发射器 export function emitter() { var subscribers = []; // 存储须要订阅的方法,并返回一个取消订阅的方法 function subscribe(sub) { subscribers.push(sub); return function () { return remove(subscribers, sub); }; } // 这里执行全部订阅方法,咱们点击页面上的按钮,执行dispatch的时候会执行订阅器里的函数 function emit(item) { var arr = subscribers.slice(); for (var i = 0, len = arr.length; i < len; i++) { arr[i](item); } } }
dispatch的时候会执行订阅器里的函数,那么订阅器里的函数是什么呢,咱们接着看第二步
方法 | 返回值 |
---|---|
task.isRunning() | 若任务还未返回或抛出了一个错误则为 true |
task.isCancelled() | 若任务已被取消则为 true |
task.result() | 任务的返回值。若任务仍在运行中则为 undefined |
task.error() | 任务抛出的错误。若任务仍在执行中则为 undefined |
task.done | 一个 Promise,如下两者之一:1.以任务的返回值 2.resolve以任务抛出的错误 reject |
task.cancel() | 取消任务(若是任务仍在执行中) |
export function runSaga(storeInterface, saga) { // iterator是封装后的rootSaga var task = proc(iterator, subscribe, wrapSagaDispatch(dispatch), getState, context, { sagaMonitor: sagaMonitor, logger: logger, onError: onError }, effectId, saga.name); return task; }
下面看看执行proc中发生了什么
上面的iterator是对rootSaga这个generator函数的封装,在proc里redux-saga会执行gen.next,就会执行到咱们的yield take('FETCH_USER');而后会返回value={TAKE:{pattern: "FETCH_USER"}},根据返回的值,判断会执行runTakeEffect()函数,在这个函数里,会注册一个taker,并把next添加到管道的taker中,到这里就结束了调用,并返回一个task。
export default function proc(iterator) { // 执行这里会把获取管道中taker的方法(chan.put)push到subscribers,因此上面第一步执行订阅中的方法其实是执行chan.put(input) var stdChannel = _stdChannel(subscribe); // 这里的task在第一次执行的时候直接返回 var task = newTask(parentEffectId, name, iterator, cont); next(); function next(arg, isErr) { var result = void 0; // iterator是封装后的rootSaga,这里执行到的是yield take('FETCH_USER'); // 返回value={TAKE:{pattern: "FETCH_USER"}} result = iterator.next(arg); // 根据返回的value,这里会执行 runEffect(result.value, parentEffectId, '', next); } }
// 这里cb是next function runTakeEffect(_ref2, cb) { var channel = _ref2.channel, pattern = _ref2.pattern, maybe = _ref2.maybe; channel = channel || stdChannel; var takeCb = function takeCb(inp) { return cb(inp); }; // 给管道注册taker,把next函数放到takers数组中 channel.take(takeCb, matcher(pattern)); }
下面是saga中间件的主要代码
// next = store.dispatch 从redux中间件得出 return function (next) { return function (action) { // dispatch(action) var result = next(action); // hit reducers // 发射全部订阅方法 sagaEmitter.emit(action); return result; }; };
下面咱们来看看sagaEmitter.emit(action)会作什么
// 发射器 export function emitter() { ... // 发射全部订阅方法 function emit(item) { var arr = subscribers.slice(); for (var i = 0, len = arr.length; i < len; i++) { arr[i](item); } } }
这里会遍历订阅函数,并执行订阅函数里的方法。在初始化的是咱们已经把获取管道中taker的方法push到订阅函数了,
因此咱们这里执行的是获取管道中的taker。
function put(input) { ... for (var i = 0; i < takers.length; i++) { var cb = takers[i]; if (!cb[MATCH] || cb[MATCH](input)) { takers.splice(i, 1); // 删除已经执行过的taker return cb(input); // 这里cb其实是next } } }
在初始化执行yield take('FETCH_USER')的时候,已经把gen.next放入到takers中,这里cb(input)其实是执行gen.next({ type: FETCH_USER }),
由于在初始化的时候gen函数已经执行了一次gen.next,如今执行gen.next则为const { data } = yield call(axios.post, 'http://rest.learncode.academy...'),同时把{ type: FETCH_USER }做为上一步的值传入。执行yeild call返回value
{CALL:{args: [url]}},根据返回值,
这里会执行源码中的
function runCallEffect(_ref4, effectId, cb) { const result = fn.apply(context, args); // 这里执行结果是promise return resolvePromise(result, cb); }
function resolvePromise(promise, cb) { // cb是gen.next,这里把yield call的返回值传递给gen.next promise.then(cb, function (error) { return cb(error, true); }); }
接下来gen.next执行到的是yield put({ type: 'UPDATE_USER', payload: data }),执行的返回值value
{PUT:{action:{payload:{id: "xx",type:"UPDATE_USER"}}}},根据返回值,
这里会执行源码中的
function runPutEffect(_ref3, cb) { var channel = _ref3.channel, action = _ref3.action; asap(function () { var result = (channel ? channel.put : dispatch)(action); // 实际上咱们演示的这段代码,这里会执行dispatch(action) return cb(result); }); }
执行dispatch(action)这里又会回到中间件中再次进入第三步的开始过程。并完成更新。
此次执行到遍历takers的地方,takers已经为空数组,会直接return,至此完成了整个获取接口到更新数据。
因为while(true)循环,再次执行yield take('FETCH_USER')。
下面附上两张执行流程图
这里只解释了执行流程和几个api,更多的请参考文档https://redux-saga-in-chinese...
本文经过简单实现了几个effect方法来地介绍了redux-saga的原理,要真正作到redux-saga的全部功能,只须要再添加一些细节就能够了
注:上面的源码均为删减版,可自行查看源码。我的文章,转载请注明出处。