浅析redux-saga实现原理
2017-10-14 08:51
295 查看
项目中一直使用redux-saga来处理异步action的流程。对于effect的实现原理感到很好奇。抽空去研究了一下他的实现。本文不会描述redux-saga的基础API和优点,单纯聊实现原理,欢迎大家在评论区留言讨论。
用generator究竟是怎么实现
要在
channel是对事件源的抽象,作用是先注册一个take方法,当put触发时,执行一次take方法,然后销毁他。
channel的简单实现如下:
我们利用channel做generator和dom事件的连接,将dom事件改写如下:
当put触发时,如果channel里已经有注册了的taker,taker就会执行。
我们需要在put触发之前,先调用channel的take方法,注册实际要运行的方法。
我们继续看mainSaga里的实现。
这个take是saga里的一种effect类型。
先看effect
出乎意料,仅仅返回了一个带类型的object。
其实redux-saga里所有effect返回的值,都是一个带类型的纯object对象。
那究竟是什么时候触发channel的take方法的呢?还需要从调用mainSaga的代码上找原因。
generator的特点是执行到某一步时,可以把控制权交给外部代码,由外部代码拿到返回结果后,决定该怎么做。
task的简易实现如下:
当
再看
到这里,我们终于看到调用channel的take方法的地方了。
完整代码如下:
整体流程就是,先通过mainSaga往channel里注册了一个taker,一旦dom点击发生,就触发channel的put,put会消耗掉已经注册的taker,这样就完成了一次点击事件的监听过程。
查看在线demo
这里用到了一个新的effect方法
我们通过添加了一种新的effect
takeEvery。
takeEvery的作用就是当channel的put发生后,自动往channel里放进一个新的taker。
我们实现的channel里同时只能有一个taker,
在线demo
通过上文的实现,我们发现所有的yield后返回的effect,都是一个纯object,用来给generator外层的执行容器task发送一个信号,告诉task该做什么。
基于这种思路,如果我们要新增一个effect,来cancel task,也可以很容易实现。
首先我们先定义一个
然后修改task的代码,让他能真正执行cancel的逻辑。
对generator使用有兴趣的同学推荐学习一下
感兴趣的同学可以关注专栏或者发送简历至chaofeng.lcf###alibaba-inc.com,欢迎有志之士加入~
原文地址:juejin.im/post/59e083…
前言
redux-saga监听action的代码如下:import { takeEvery } from 'redux-saga'; function* mainSaga() { yield takeEvery('action_name', function* (action) { console.log(action); }); }
用generator究竟是怎么实现
takeEvery的呢?我们先来看稍微简单一点的
take的实现原理:
take实现原理
我们尝试写一个demo,用saga的方式实现用generator监听action。$btn.addEventListener('click', () => { const action =`action data${i++}`; // trigger action }, false); function* mainSaga() { const action = yield take(); console.log(action); }
要在
$btn点击时候,能够读到action的值。
channel
这里我们需要引入一个概念——channel。
channel是对事件源的抽象,作用是先注册一个take方法,当put触发时,执行一次take方法,然后销毁他。
channel的简单实现如下:
function channel() { let taker; function take(cb) { taker = cb; } function put(input) { if (taker) { const tempTaker = taker; taker = null; tempTaker(input); } } return { put, take, }; } const chan = channel();
我们利用channel做generator和dom事件的连接,将dom事件改写如下:
$btn.addEventListener('click', () => { const action =`action data${i++}`; chan.put(action); }, false);
当put触发时,如果channel里已经有注册了的taker,taker就会执行。
我们需要在put触发之前,先调用channel的take方法,注册实际要运行的方法。
我们继续看mainSaga里的实现。
function* mainSaga() { const action = yield take(); console.log(action); }
这个take是saga里的一种effect类型。
先看effect
take()的实现。
function take() { return { type: 'take' }; }
出乎意料,仅仅返回了一个带类型的object。
其实redux-saga里所有effect返回的值,都是一个带类型的纯object对象。
那究竟是什么时候触发channel的take方法的呢?还需要从调用mainSaga的代码上找原因。
generator的特点是执行到某一步时,可以把控制权交给外部代码,由外部代码拿到返回结果后,决定该怎么做。
task
这里我们又要引入一个新的概念task。
task是generator方法的执行环境,所有saga的generator方法都跑在task里。
task的简易实现如下:
function task(iterator) { const iter = iterator(); function next(args) { const result = iter.next(args); if (!result.done) { const effect = result.value; if (effect.type === 'take) { runTakeEffect(result.value, next); } } } next(); } task(mainSaga);
当
yield take()运行时,将
take()返回的结果交给外层的task,此时代码的控制权就已经从gennerator方法中转到了task里了。
result.value的值就是
take()返回的结果
{ type: 'take' }。
再看
runTakeEffect的实现:
function runTakeEffect(effect, cb) { chan.take(input => { cb(input); }); }
到这里,我们终于看到调用channel的take方法的地方了。
完整代码如下:
function channel() { let taker; function take(cb) { taker = cb; } function put(input) { if (taker) { const tempTaker = taker; taker = null; tempTaker(input); } } return { put, take, }; } const chan = channel();
function take() { return { type: 'take' }; }
function* mainSaga() { const action = yield take(); console.log(action); }
function runTakeEffect(effect, cb) { chan.take(input => { cb(input); }); }
function task(iterator) {
const iter = iterator();
function next(args) {
const result = iter.next(args);
if (!result.done) {
const effect = result.value;
if (effect.type === 'take') {
runTakeEffect(result.value, next);
}
}
}
next();
}
task(mainSaga);
let i = 0;
$btn.addEventListener('click', () => { const action =`action data${i++}`; chan.put(action); }, false);
整体流程就是,先通过mainSaga往channel里注册了一个taker,一旦dom点击发生,就触发channel的put,put会消耗掉已经注册的taker,这样就完成了一次点击事件的监听过程。
查看在线demo
takeEvery实现原理
在上一节中,我们已经模仿saga实现了一次事件监听,但是还是有问题,我们只能监听一次点击,怎么能做到监听每次点击事件呢?redux-saga提供了一个helper方法——takeEvery。我们尝试在我们的简易版saga中实现一下
takeEvery。
function* takeEvery(worker) { yield fork(function* () { while(true) { const action = yield take(); worker(action); } }); } function* mainSaga() { yield takeEvery(action => { $result.innerHTML = action; }); }
这里用到了一个新的effect方法
fork。
fork
fork的作用是启动一个新的task,不阻塞原task执行。代码修改如下:function fork(cb) { return { type: 'fork', fn: cb, }; } function runForkEffect(effect, cb) { task(effect.fn || effect); cb(); } function task(iterator) { const iter = typeof iterator === 'function' ? iterator() : iterator; function next(args) { const result = iter.next(args); if (!result.done) { const effect = result.value; // 判断effect是否是iterator if (typeof effect[Symbol.iterator] === 'function') { runForkEffect(effect, next); } else if (effect.type) { switch (effect.type) { case 'take': runTakeEffect(effect, next); break; case 'fork': runForkEffect(effect, next); break; default: } } } } next(); }
我们通过添加了一种新的effect
fork,启动了一个新的task
takeEvery。
takeEvery的作用就是当channel的put发生后,自动往channel里放进一个新的taker。
我们实现的channel里同时只能有一个taker,
while(true)的作用就是每当一个put触发消耗掉了taker后,就自动触发
runTakeEffect中传入的task的next方法,再次往channel里放进一个taker,从而做到源源不断地监听事件。
在线demo
effect的本质
通过上文的实现,我们发现所有的yield后返回的effect,都是一个纯object,用来给generator外层的执行容器task发送一个信号,告诉task该做什么。基于这种思路,如果我们要新增一个effect,来cancel task,也可以很容易实现。
首先我们先定义一个
cancel方法,用来发送cancel的信号。
function cancel() { return { type: 'cancel' }; }
然后修改task的代码,让他能真正执行cancel的逻辑。
function task(iterator) { const iter = typeof iterator === 'function' ? iterator() : iterator; ... function runCancelEffect() { // do some cancel logic } function next(args) { const result = iter.next(args); if (!result.done) { const effect = result.value; if (typeof effect[Symbol.iterator] === 'function') { runForkEffect(effect, next); } else if (effect.type) { switch (effect.type) { case 'cancel': runCancelEffect(); case 'take': runTakeEffect(result.value, next); break; case 'fork': runForkEffect(result.value, next); break; default: } } } } next(); }
小结
本文通过简单实现了几个effect方法来地介绍了redux-saga的原理,要真正做到redux-saga的所有功能,只需要再添加一些细节就可以了。大概如下图所示:对generator使用有兴趣的同学推荐学习一下
redux-saga源码。在此推荐一篇使用generator实现dom事件监听的文章 继续探索JS中的Iterator,兼谈与Observable的对比
感兴趣的同学可以关注专栏或者发送简历至chaofeng.lcf###alibaba-inc.com,欢迎有志之士加入~
原文地址:juejin.im/post/59e083…
相关文章推荐
- .NET 1.1中预编译ASP.NET页面实现原理浅析 [1] 自动预编译机制浅析(转贴)
- Netty实现原理浅析
- .NET 1.1中预编译ASP.NET页面实现原理浅析
- assert的实现原理浅析
- Netty实现原理浅析
- 浅析 Linux 中的时间编程和实现原理
- 浅析AOP实现原理(1)静态代理
- 浅析JSONP技术原理及实现
- CLR 中匿名函数的实现原理浅析
- .NET 2.0 中 GetDelegateForFunctionPointer 函数实现原理浅析 [草稿]
- 如何在App中实现朋友圈功能之一朋友圈实现原理浅析——箭扣科技Arrownock
- Win32 临界区实现原理浅析
- 用WinDbg探索CLR世界 [10] 透明代理实现原理浅析
- 浅析SkipList跳跃表原理及代码实现
- java NIO Netty实现原理浅析(转)
- Netty实现原理浅析
- 浅析基于中间层驱动的包过滤原理与实现
- Netty构建分布式消息队列实现原理浅析
- Android微信抢红包功能的实现原理浅析
- hashmap实现原理浅析