与每个复杂的问题一样,您必须将其分解为更小的、易于理解的问题。
因此,我创建了一个堆栈闪电战 https://stackblitz.com/edit/typescript-pjytrb?file=index.ts重新创建基本功能的演示:
- 启动计时器
- 暂时停止(标记时间戳);例如,当选择所有答案时
- 从最后一个时间戳继续
- 重置定时器
- 停止计时器
这是代码:
const $ = document.querySelector.bind(document);
const start$ = fromEvent($('#start'), 'click').pipe(shareReplay(1));
const reset$ = fromEvent($('#reset'), 'click');
const stop$ = fromEvent($('#stop'), 'click');
const markTimestamp$ = fromEvent($('#mark'), 'click');
const continueFromLastTimestamp$ = fromEvent($('#continue'), 'click');
const src$ = concat(
start$.pipe(first()),
reset$
).pipe(
switchMapTo(
timer(0, 1000)
.pipe(
takeUntil(markTimestamp$),
repeatWhen(
completeSbj => completeSbj.pipe(switchMapTo(
continueFromLastTimestamp$.pipe(first())
))
),
scan((acc, crt) => acc + 1000, 0)
)
),
takeUntil(stop$),
repeatWhen(completeSbj => completeSbj.pipe(switchMapTo(start$.pipe(skip(1), first()))))
).subscribe(console.log)
让我们逐一了解每个相关部分。
concat(
start$.pipe(first()),
reset$
).pipe(switchMapTo(timer(...)))
The timer
仅当之前未开始时才会从 0 开始计数(start$.pipe(first())
)或者用户想要重置所有内容(reset$
).
concat(a$, b$)
确保b$
不能发射,除非a$
完成。
timer(0, 1000)
.pipe(
takeUntil(markTimestamp$),
repeatWhen(
completeSbj => completeSbj.pipe(switchMapTo(
continueFromLastTimestamp$.pipe(first())
))
),
scan((acc, crt) => acc + 1000, 0)
)
我们希望计时器一直处于活动状态,直到markTimestamp$
发出。当这种情况发生时,源(timer(0, 1000)
) 将被取消订阅。和repeatWhen
我们可以决定什么时候应该timer
重新订阅。也就是说,当continueFromLastTimestamp$.pipe(first())
发出。重要的是我们使用first()
,否则源可能会被重新订阅多次。
Placing scan((acc, crt) => acc + 1000, 0)
after repeatWhen
确保最后的时间戳不会丢失。例如,计时器可能开始于X
, at X+5
用户触发markTimestamp$
然后在X + 100
用户触发continueFromLastTimestamp$
。发生这种情况时,计时器将发出X+6
.
takeUntil(stop$),
repeatWhen(completeSbj => completeSbj.pipe(switchMapTo(start$.pipe(skip(1), first()))))
计时器应处于活动状态,直到stop$
发出。然后,只有用户触发才能重新启动start$
again. skip(1)
使用是因为我们不使用缓存值ReplaySubject
被使用过start$
's shareReplay
and first()
使用是因为source应该重新订阅 只有一次.