在RxJS中创建一个打字计时器;跟踪打字时间 [英] Making a typing timer in RxJS; Tracking time spent typing

查看:108
本文介绍了在RxJS中创建一个打字计时器;跟踪打字时间的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题是我之前提出的问题的延伸,你可以在这里找到:

This question is an extension of my previous question that you can find here:

如何使用RxJS显示用户正在输入#41521028指标?

在成功跟踪用户是否正在键入之后,我需要能够将该特定状态用作触发器时钟。

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:


  1. 我首先构建一个流来捕获每个输入事件。

  2. 对于这些事件中的每一个,我将使用 switchMap 来:(a)触发原来的TYPING事件,这样我们就不会失去它,并且(b)在1秒后发射空闲事件。您可以看到我将它们创建为单独的流,然后将它们合并在一起。这样,我得到一个流,它将指示输入框的输入状态。

  3. 我创建了第二个流,每秒发送一个事件。使用 withLatestFrom ,我将此流与先前的输入状态流组合在一起。现在它们组合在一起,我可以检查输入状态是IDLE还是TYPING。如果他们正在打字,我给该事件的值为 1 ,否则为 0

  4. 现在我有一个 1 0 的流,我所要做的就是添加他们用 .scan()备份并将其呈现给DOM。

  1. I first build a stream to capture each typing event.
  2. 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.
  3. 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 of 1, otherwise a 0.
  4. Now I have a stream of 1s and 0s, 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);

JSBin现场演示

根据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);

JSBin现场演示

推荐答案

我发现你的代码存在一些问题。它的要点是好的,但如果你使用不同的操作符,你可以更容易地做同样的事情。

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆