昨天我们讲到开始和停止一个事件流,回顾一下代码:
const stopBtnClick$ = fromEvent(stopBtnRef.current, "click");
const startBtnClick$ = fromEvent(startBtnRef.current, "click");
const perSecond$ = interval(1000);
const intervalCanBeStopped$ = perSecond$
.pipe(takeUntil(stopBtnClick$));
const subscription = startBtnClick$.pipe(
switchMapTo(intervalCanBeStopped$)
).subscribe(v => console.log(v));
现在的情况是,当我们点击停止按钮后再次点击开始按钮,计数又是从 0 开始。如果我们想从停止时的数字继续计数呢?按照常规的编程模型,我们会这样做:
let count = 0;
const subscription = startBtnClick$
.pipe(
switchMapTo(intervalCanBeStopped$),
)
.subscribe(v => console.log(count++));
这样做,确实可以实现从暂定点开始恢复计数。但:
不要这样做!!!
不要这样做!!!
不要这样做!!!
在 Rx 编程模型中,正确的做法是使用 scan 操作符。scan 操作符就像是数组的 reduce 函数。它的使用方法是:
scan((acc, value,index) => acc, seed)
scan 的参数有两个,一个是累积函数,是必须提供的;另一个是起始值,为可选参数。累积函数中的 acc 参数是累积值,value 是原始流的值,index 是索引,返回值是累计值。返回值会作为下次函数运行的参数。
具体使用方法如下:
startBtnClick$
.pipe(
switchMapTo(intervalCanBeStopped$),
scan((acc)=>{count: acc.count + 1}, {count:0})
)
这里我们没有使用原始流中值,我们的累计值的初始值是个对象,对象里面有个 count 属性,值为 0 : {count:0},也就是对应函数参数中的 acc。累积函数的返回值为累计值中的 count 属性 +1。这样就完成了暂停功能。
下面我们来看看另一种为 scan 提供初始值的方法。
startWith:这个操作符的作用是在原始流产生值之前插入提供给它参数值。
我们的代码改动如下:
startBtnClick$
.pipe(
switchMapTo(intervalCanBeStopped$),
startWith({count: 0}),
scan((acc)=>{count: acc.count + 1})
)
startWith 就像是加塞儿,intervalCanbeStopped$ 产生了第一个值,碰到 startWith 后,startWith 中的参数将加塞到这值之前传递给 scan 。我们知道 scan 操作符在不提供第二个参数时,会使用原始流传递过来的第一个值作为初始值。因此,startWith 中的参数成为了 scan 的初始值。
今天使用到的两个操作符将在前端中的状态管理,网络请求中经常使用到。今后的文章中会提到。
如有任何问题,请关注公众号“读一读我”。
网友评论