本文主要是介绍超级易懂的redux-saga原理解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
原文地址
前言
笔者最近在做一些后台项目,使用的是Ant Design Pro,其使用了redux-saga处理异步数据流,本文将对redux-saga的原理做一个简单的解读,并将实现一个简易版的redux-saga。
Generator函数的自动流程控制
在redux-saga中,saga是指一些长时操作,用generator函数表示。generator函数的强大之处在于其可以手动的暂停、恢复执行,且可以与函数体外进行数据交互,看如下例子:
function *gen() {const a = yield 'hello';console.log(a);
}cont g = gen();
g.next(); // { value: 'hello', done: false }
setTimeout(() => g.next('hi'), 1000) // 此时 a => 'hi' 一秒后打印‘hi'
复制代码
可以看出来genrator函数何时进行下一步操作完全取决于外部的调度时机,且其内部执行状态也由外部的输入决定,这使得generator函数可以很方便的做异步流程控制。举个例子,我们首先读取一个文件的内容作为查询参数,然后请求一个查询接口并把返回的内容打印出来:
function getParams(file) {return new Promise(resolve => {fs.readFile(file, (err, data) => {resolve(data)})})
}function getContent(params) {// request返回promisereturn request(params)
}function *gen() {const params = yield getParams('config.json');const content = yield getContent(params);console.log(content);
}
复制代码
我们可以手动控制gen函数的执行:
const g = gen();
g.next().value.then(params => {g.next(params).value.then(content => {g.next(content);})
})
复制代码
以上可以达到我们的目的,但是过于繁琐,我们想要的是generator函数可以自动的执行,可以写一个简易的自动执行函数如下:
function genRun(gen) {const g = gen();next();function next(err, pre) {let temp;(err === null) && (temp = g.next(pre));(err !== null) && (temp = g.throw(pre));if(!temp.done) {nextWithYieldType(temp.value, next);}}
}function nextWithYieldType(value, next) {if(isPromise(value)) {value.then(success => next(null, success)).catch(error => next(error))}
}genRun(gen);
复制代码
此时generator函数便可以自动执行,事实上我们可以发现,generator的内部状态完全是由nextWithYieldType
决定的,我们可以根据yield的类型执行不同的处理逻辑。
Effect
事实上sagaMiddleware.run(saga)
可以类似看做genRun(saga)
,而saga是由一个个的effect组成的,那么effect是什么?redux-saga官网的解释:一个 effect 就是一个 Plain Object JavaScript 对象,包含一些将被 saga middleware 执行的指令。redux-saga提供了很多Effect创建器,如call
、put
、take
等,已call
为例:
function saga*() {const result = yield call(genPromise);console.log(result);
}
复制代码
call(genPromise)
生成的就是一个effect,它可能类似如下:
{isEffect: true,type: 'CALL',fn: genPromise
}
复制代码
事实上effect只表明了意图,而实际的行为由类似于上文的nextWithYieldType完成,例如:
function nextWithYieldType(value, next) {...if(isCallEffect(value)) {value.fn(). then(success => next(null, success)).catch(error => next(error)) }
}
复制代码
当genPromise函数返回的promise被resolve后便会打印出结果。
生产者与消费者
观察下面的例子
function *saga() {yield take('TEST');console.log('test...');
}sagaMiddleware.run(test);
复制代码
saga会在take('TEST')
处阻塞,只有执行了dispatch({type: 'TEST'})
后saga才能继续运行(注意:此时的dispatch
方法是经过sagaMiddleware包装过的)。这给我们的感觉似乎很像是take
是一个生产者,在等待disaptch
的消费,事实上take
只是一个Effect生成器,具体的处理逻辑依然是在nextWithYieldType完成的,类似于:
function nextWithYieldType(value, next) {...// take('TEST')生成的effect简单的认为是 {isEffect: true, type: 'TAKE', name: 'TEST'}if(isTakeEffect(value)) {channel.take({pattern: value.name, cb: params => next(null, params)}) }
}
复制代码
channel是一个任务生成器,它有两个方法:take生成任务,put消费任务:
function channel() {/*task = {pattern,cb}*/let _task = null;function take(task) {_task = task;}function put(pattern, args) {if(!_task) return;if(pattern == _task.pattern) _task.cb.call(null, args);}return {take,put}
}
复制代码
显然任务是在执行dispatch
的时候被消费掉的,这个工作是在sagaMiddleware中做的,类似于如下:
const sagaMiddleware = store => {return next => action => {next(action);const { type, ...payload } = action;channel.put(type, payload);}
}
复制代码
看到这里我们可以发现,需要我们做的就是不断的完善nextWithYieldType这个函数,当完成了put
、fork
、takeEvery
对应的逻辑后,一个具备基本功能的redux-saga就诞生啦,笔者就不在赘述这些功能的实现了。最后,你可以查看这里:tiny-redux-saga,这是笔者实现的一个简易版的redux-saga,希望对你有所帮助。
全文完。
作者:菜菜_张
链接:https://juejin.im/post/5bd5bd8bf265da0aca33544d
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这篇关于超级易懂的redux-saga原理解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!