我们知道 takeWhile 操作符在条件函数不满足时立即触发流的 complete 事件,所表现出来的效果就是点击任何按钮都不再有任何反应。
const addOneOrReset = (time = 1000) =>
merge(
interval(time).pipe(
takeUntil(pauseBtnClick$),
mapTo(addOne)
),
resetBtnClick$.pipe(mapTo(reset))
);
const time$ = merge(
startBtnClick$.pipe(mapTo(1000)),
halfBtnClick$.pipe(mapTo(500)),
quarterBtnClick$.pipe(mapTo(250))
);
const timer$ = time$.pipe(
switchMap(addOneOrReset),
startWith({ count: 0 }),
scan((acc, current) => current(acc)),
map(obj => obj.count),
tap(v => setTxt(v))
);
const subscription = timer$
.pipe(
takeWhile(data => data < 5),
)
.subscribe(
x => console.log(x),
err => console.log(err),
() => console.log("complete")
);
当我们点击开始按钮时,定时器流开始产生事件。由于 takeWhile 操作符的原因,到 5 时控制台将输出 complete。
这里先说明下界面和控制台输出的数字为什么不一样。因为界面更新是在 tap 操作符做的,要先于 takeWhile 操作符。当界面更新为 5 时,takeWhile 操作停止了事件流。
这时,我们再点击界面上的任何按钮都没有任何反应了。
现在我们可以把这个定时器想象成一个 5 秒倒计时器,那我们的需求来了,现在的倒计时器是一次性的(除非刷新页面),如何能让它在一次倒计时完成后恢复到起始状态呢?这时,repeat 操作符闪亮登场了。
下面只把变化的代码贴出来:
const subscription = timer$
.pipe(
takeWhile(data => data < 5),
repeat()
)
.subscribe(
x => console.log(x),
err => console.log(err),
() => console.log("complete")
);
我们在 takeWhile 操作符下面添加了 repeat 操作符,这时的效果如下图:
倒计时清零的需求完成了。这时我们还发现,控制台再也没有输出 complete ,这时因为我们没有给 repeat 传入任何参数,代表无限重复执行。大家可以自己试试传入了参数是什么效果。
下面要重点说明的是,使用 repeat 操作符的位置将影响它的行为,这里交给读者自己去尝试,比如把 repeat 放在 takeWhile 的前面会是什么效果。使用 repeat 操作符的最佳实践是放在离订阅函数最近的位置。
接下来,我们先来看看下面的代码会有什么输出:
const timer$ = time$.pipe(
switchMap(addOneOrReset),
startWith({ count: 0 }),
scan((acc, current) => current(acc)),
map(obj => obj.count),
tap(v => setTxt(v)),
);
timer$.subscribe(console.log)
timer$.subscribe(console.log)
我们订阅了两遍定时器流,控制台输出了两遍 0 ,这合情合理,每个订阅函数拥有独立的定时器流。但假如我们想让后来的订阅函数永远得到的是最新内容呢?就像订阅杂志,你得到杂志是从你订阅的时间以后的杂志,总不能把以前 20 年的杂志都给你吧?share 操作符可以帮你完成这个需求。我们只需在定时器流的最后加入 share,那么第二个 subscribe 函数将不会打印输出,这个就交给大家自己试试吧。
const timer$ = time$.pipe(
switchMap(addOneOrReset),
startWith({ count: 0 }),
scan((acc, current) => current(acc)),
map(obj => obj.count),
tap(v => setTxt(v)),
share(),// <--- 这里
);
如有任何问题,更多内容请关注微信公众号“读一读我”(只给你实际工作用得上的干货)。
网友评论