Как использовать RxJS для отображения индикатора "пользователь печатает"?

Я немного знаю BaconJS, но сейчас я пытаюсь изучить RxJS, создав индикатор "Пользователь печатает...". Это довольно просто, это можно объяснить двумя простыми правилами:

  1. Когда пользователь печатает, индикатор должен быть сразу виден.
  2. Когда пользователь прекращает печатать, индикатор должен оставаться видимым в течение 1 секунды после последнего действия пользователя.

Я не уверен, что это правильно, но я до сих пор создал два потока:

  1. Один поток сердцебиения, который испускает 0 каждую секунду.
  2. Один поток, чтобы захватить пользовательские события ввода и испустить 1 для каждого события.

Затем я объединяю их вместе и просто нажимаю на результат. Если это 1Затем я показываю индикатор. Если это 0затем я скрываю индикатор.

Вот как это выглядит:

const showTyping = () =>
  $('.typing').text('User is typing...');

const showIdle = () =>
  $('.typing').text('');

// 1 second heartbeats are mapped to 0
const heartbeat$ = Rx.Observable
  .interval(1000)
  .mapTo(0);

// user typing events are mapped to 1
const input$ = Rx.Observable
  .fromEvent($('#input'), 'input')
  .mapTo(1);

// we merge the streams together
const state$ = heartbeat$
  .merge(input$)
  .do(val => val === 0 ? showIdle() : showTyping())
  .subscribe(console.log);

Вот ссылка на JSBin:

http://jsbin.com/vekixuv/edit?js,console,output

У меня есть несколько проблем и вопросов, связанных с этой реализацией:

  1. Иногда, когда пользователь печатает, 0 пробирается, поэтому индикатор мигает на доли секунды, прежде чем вернуться к следующему нажатию клавиши пользователя.
  2. Не гарантируется, что индикатор исчезнет через 1 секунду после того, как пользователь перестанет печатать. Гарантируется, что индикатор исчезнет в течение 1 секунды (что противоположно тому, что мы хотим).
  3. Является ли использование потока сердцебиения правильным способом RxJS для этого? У меня есть чувство, что это не может быть.

У меня есть ощущение, что я совершенно не согласен с моей реализацией, я ценю любую помощь, которую вы можете оказать. Благодарю.

2 ответа

Решение

Вам даже не нужно использовать два Observables и использовать только один с debounceTime(), Вся логика, которую вы пытались сделать, уже присутствует в debounceTime() оператор:

const showTyping = () =>
  $('.typing').text('User is typing...');

const showIdle = () =>
  $('.typing').text('');

const input$ = Rx.Observable
  .fromEvent($('#input'), 'input')
  .do(() => showTyping())
  .debounceTime(1000)
  .subscribe(() => showIdle());

Смотрите живую демонстрацию: http://jsbin.com/cixipa/6/edit?js,console,output

Для этого вам не нужно сердцебиение, просто генерируйте изменения-события всякий раз, когда что-то происходит / изменяется:

const showTyping = () =>
  $('.typing').text('User is typing...');

const showIdle = () =>
  $('.typing').text('');


// user typing events
const input$ = Rx.Observable
  .fromEvent($('#input'), 'input');

// user stopped typing
const stoppedTypingAfter1s$ = input$
  .switchMapTo(Rx.Observable.timer(1000));

// we merge the streams together
const state$ = Rx.Observable.merge(
    input$.mapTo(1),
    stoppedTypingAfter1s$.mapTo(0)
)
  .startWith(0)
  .do(val => val === 0 ? showIdle() : showTyping())
  .subscribe(console.log);

Смотрите в прямом эфире здесь.

switchMap, будет сбрасывать любой предыдущий 1-таймер всякий раз, когда генерируется новое событие ввода.

Другие вопросы по тегам