在RxJS中创建一个打字计时器;跟踪打字时间 [英] Making a typing timer in RxJS; Tracking time spent typing
问题描述
这个问题是我之前提出的问题的延伸,你可以在这里找到:
This question is an extension of my previous question that you can find here:
在成功跟踪用户是否正在键入之后,我需要能够将该特定状态用作触发器时钟。
After having successfully been able to track whether or not the user was typing, I needed to be able to use that particular state as a trigger for a clock.
逻辑很简单,基本上我想在用户输入时运行一个时钟。但是当用户停止输入时,我需要暂停时钟。当用户再次开始输入时,时钟应该继续累积。
The logic is simple, essentially I want a clock to be run when the user is typing. But when the user stops typing, I need the clock to pause. When the user starts typing again, the clock should continue to accumulate.
我已经能够让它工作,但它看起来像一团糟我需要帮助重构它所以它不是一个意大利面条的球。以下是代码的样子:
I have already been able to get it to work, but it looks like a mess and I need help refactoring it so it isn't a ball of spaghetti. Here is what the code looks like:
/*** 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
And here is the link to the live JSBin: http://jsbin.com/lazeyov/edit?js,console,output
也许我将引导您完成代码的逻辑:
Perhaps I will walk you through the logic of the code:
- 我首先构建一个流来捕获每个输入事件。
- 对于这些事件中的每一个,我将使用
switchMap
来:(a)触发原来的TYPING事件,这样我们就不会失去它,并且(b)在1秒后发射空闲事件。您可以看到我将它们创建为单独的流,然后将它们合并在一起。这样,我得到一个流,它将指示输入框的输入状态。 - 我创建了第二个流,每秒发送一个事件。使用
withLatestFrom
,我将此流与先前的输入状态流组合在一起。现在它们组合在一起,我可以检查输入状态是IDLE还是TYPING。如果他们正在打字,我给该事件的值为1
,否则为0
。 - 现在我有一个
1
和0
的流,我所要做的就是添加他们用.scan()
备份并将其呈现给DOM。
- I first build a stream to capture each typing event.
- For each of these events, I will use
switchMap
to: (a) fire off the original "TYPING" event so we don't lose it, and (b) fire off an "IDLE" event, 1 second later. You can see that I create these as separate streams and then merge them together. This way, I get a stream that will indicate the "typing state" of the input box. - I create a second stream that sends an event every second. Using
withLatestFrom
, I combine this stream with the previous "typing state" stream. Now that they are combined, I can check whether or not the typing state is "IDLE" or "TYPING". If they are typing, I give the event a value of1
, otherwise a0
. - Now I have a stream of
1
s and0
s, all I have to do is add them back up with.scan()
and render it to the DOM.
RxJS编写此功能的方式是什么?
What is the RxJS way to write this functionality?
基于@ osln的回答。
Based on @osln's answer.
/*** 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);
根据Dorus的回答。
Based on Dorus' answer.
/*** 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);
推荐答案
我发现你的代码存在一些问题。它的要点是好的,但如果你使用不同的操作符,你可以更容易地做同样的事情。
I notice a few problems with your code. The gist of it is good, but if you use different operators you can do the same thing even easier.
首先你使用 switchMap
,这个每次新输入到达时,它都是一个很好的运算符来启动新流。但是,您真正想要的是只要用户输入就继续当前计时器。这里一个更好的运营商是 exhaustMap
因为 exhaustMap
将保持已经活动的计时器直到它停止。如果用户没有键入1秒钟,我们就可以停止活动计时器。这可以通过 .takeUntil(input.debounceTime(1000))
轻松完成。这将导致非常短的查询:
First you use switchMap
, this is a nice operator to start a new stream every time a new input arrives. However, what you really want is to continue the current timer as long as the user is typing. A better operator here would be exhaustMap
because exhaustMap
will keep the already active timer until it stops. We can then stop the active timer if the user is not typing for 1 second. That is easily done with .takeUntil(input.debounceTime(1000))
. That would result in the very short query:
input.exhaustMap(() => Rx.Observable.timer(1000).takeUntil(input.debounceTime(1000)))
对于这个查询,我们可以挂钩显示事件你想要的, showTyping
, showIdle
等我们还需要修复计时器 index
,因为每次用户停止输入时它都会重置。这可以通过在 map
,因为这是当前流中值的索引。
To this query, we can hook the display events you want, showTyping
, showIdle
etc. We also need to fix the timers index
, as it will reset every time the user stops typing. This can be done with using the second parameter of project function in map
, as this is the index of the value in the current stream.
Rx.Observable.fromEvent($('#input'), 'input')
.publish(input => input
.exhaustMap(() => {
showTyping();
return Rx.Observable.interval(1000)
.takeUntil(input.startWith(0).debounceTime(1001))
.finally(showIdle);
})
).map((_, index) => index + 1) // zero based index, so we add one.
.subscribe(updateTimer);
注意我在这里使用发布
,但它由于来源很热,因此不是严格需要的。无论如何推荐,因为我们使用输入
两次,现在我们不必考虑它是热还是冷。
Notice i used publish
here, but it is not strictly needed as the source is hot. However recommended because we use input
twice and now we do not have to think about if it's hot or cold.
/*** Helper Functions ***/
const showTyping = () =>
$('.typing').text('User is typing...');
const showIdle = () =>
$('.typing').text('');
const updateTimer = (x) =>
$('.timer').text(x);
/*** Program Logic ***/
Rx.Observable.fromEvent($('#input'), 'input')
.publish(input => input
.exhaustMap(() => {
showTyping();
return Rx.Observable.interval(1000)
.takeUntil(input.startWith(0).debounceTime(1001))
.finally(showIdle);
})
).map((_, index) => index + 1) // zero based index, so we add one.
.subscribe(updateTimer);
<head>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<script src="https://unpkg.com/@reactivex/rxjs@5.0.0-beta.12/dist/global/Rx.js"></script>
</head>
<body>
<div>
<div>Seconds spent typing: <span class="timer">0</span></div>
<input type="text" id="input">
<div class="typing"></div>
</div>
</body>
这篇关于在RxJS中创建一个打字计时器;跟踪打字时间的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!