这个问题是我之前的问题的延伸,你可以在这里找到:
在成功跟踪用户是否正在打字之后,我需要能够使用该特定状态作为时钟的触发器。
逻辑很简单,本质上我希望在用户打字时运行一个时钟。但是当用户停止打字时,我需要时钟暂停。当用户再次开始打字时,时钟应继续累积。
我已经能够让它工作,但它看起来一团糟,我需要帮助重构它,所以它不是意大利面条球。下面是代码的样子:
/*** Render Functions ***/
const showTyping = () =>
$('.typing').text('User is typing...');
const showIdle = () =>
$('.typing').text('');
const updateTimer = (x) =>
$('.timer').text(x);
/*** Program Logic ***/
const typing$ = Rx.Observable
.fromEvent($('#input'), 'input')
.switchMapTo(Rx.Observable
.never()
.startWith('TYPING')
.merge(Rx.Observable.timer(1000).mapTo('IDLE')))
.do(e => e === 'TYPING' ? showTyping() : showIdle());
const timer$ = Rx.Observable
.interval(1000)
.withLatestFrom(typing$)
.map(x => x[1] === 'TYPING' ? 1 : 0)
.scan((a, b) => a + b)
.do(console.log)
.subscribe(updateTimer)
这是实时 JSBin 的链接:http ://jsbin.com/lazeyov/edit?js,console,output
也许我会带你了解代码的逻辑:
- 我首先构建一个流来捕获每个打字事件。
- 对于这些事件中的每一个,我将使用
switchMap
:(a) 触发原始的“TYPING”事件,这样我们就不会丢失它,以及 (b) 1 秒后触发“IDLE”事件。您可以看到我将它们创建为单独的流,然后将它们合并在一起。这样,我得到一个流,将指示输入框的“输入状态”。 - 我创建了第二个流,每秒发送一个事件。使用
withLatestFrom
,我将此流与之前的“输入状态”流结合起来。现在它们组合在一起,我可以检查打字状态是“空闲”还是“打字”。如果他们正在打字,我给事件一个值1
,否则一个0
。 - 现在我有一个
1
s 和0
s流,我所要做的就是将它们添加回来并将其.scan()
呈现给 DOM。
编写此功能的 RxJS 方式是什么?
编辑:方法 1 — 构建更改事件流
基于@osln 的回答。
/*** Helper Functions ***/
const showTyping = () => $('.typing').text('User is typing...');
const showIdle = () => $('.typing').text('');
const updateTimer = (x) => $('.timer').text(x);
const handleTypingStateChange = state =>
state === 1 ? showTyping() : showIdle();
/*** Program Logic ***/
const inputEvents$ = Rx.Observable.fromEvent($('#input'), 'input').share();
// streams to indicate when user is typing or has become idle
const typing$ = inputEvents$.mapTo(1);
const isIdle$ = inputEvents$.debounceTime(1000).mapTo(0);
// stream to emit "typing state" change-events
const typingState$ = Rx.Observable.merge(typing$, isIdle$)
.distinctUntilChanged()
.share();
// every second, sample from typingState$
// if user is typing, add 1, otherwise 0
const timer$ = Rx.Observable
.interval(1000)
.withLatestFrom(typingState$, (tick, typingState) => typingState)
.scan((a, b) => a + b, 0)
// subscribe to streams
timer$.subscribe(updateTimer);
typingState$.subscribe(handleTypingStateChange);
编辑:方法 2 — 在用户开始输入时使用exhaustMap 启动计数器
基于多鲁斯的回答。
/*** Helper Functions ***/
const showTyping = () => $('.typing').text('User is typing...');
const showIdle = () => $('.typing').text('');
const updateTimer = (x) => $('.timer').text(x);
/*** Program Logic ***/
// declare shared streams
const inputEvents$ = Rx.Observable.fromEvent($('#input'), 'input').share();
const idle$ = inputEvents$.debounceTime(1000).share();
// intermediate stream for counting until idle
const countUntilIdle$ = Rx.Observable
.interval(1000)
.startWith('start counter') // first tick required so we start watching for idle events right away
.takeUntil(idle$);
// build clock stream
const clock$ = inputEvents$
.exhaustMap(() => countUntilIdle$)
.scan((acc) => acc + 1, 0)
/*** Subscribe to Streams ***/
idle$.subscribe(showIdle);
inputEvents$.subscribe(showTyping);
clock$.subscribe(updateTimer);