RxJS DOM пауза наблюдается, в то время как другой "тянет"?

ОБНОВИТЬ

Я попытался сделать автономную версию здесь: https://codepen.io/neezer/pen/pPRJar

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

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

Также я использую RxJS v5 и последнюю версию React.


Все еще изучаю RxJS...

У меня есть два Observable: один подписан на координаты мыши при наведении на таблицу, чтобы показать столбец изменения размера, а другой - чтобы пользователь мог перетаскивать этот столбец.

Грубо говоря, первый выглядит так (все нижеуказанное определено в componentDidUpdate метод жизненного цикла в компоненте React):

Rx.DOM.mouseover(tableEl)
  .map(/* some complicated x coordinate checking */)
  .distinctUntilChanged()
  .subscribe(/* setState call */)

Это прекрасно работает, и дает мне это:

IMG

Итак, теперь я хочу предоставить фактическое поведение "перетаскивания", и я попытался настроить новый Observable, как это

// `resizerEl` is the black element that appears on hover
// from the previous observable; it's just a div that gets
// repositioned and conditionally created
Rx.DOM.mousedown(resizerEl)
  .flatMap(md => {
    md.preventDefault()

    return Rx.DOM.mousemove(tableEl)
      .map(mm => mm.x - md.x)
      .takeUntil(Rx.DOM.mouseup(document))
  })
  .subscribe(/* do column resizing stuff */)

Есть три проблемы с этим:

  1. Как только я совершил свое первое "перетаскивание", я больше не могу. Я понимаю, что takeUntil завершает Observable, и я не уверен, как я могу "перезапустить" его.
  2. mousemove с первой наблюдаемой все еще активен, пока я тащу, поэтому мой черный div исчезнет, ​​как только мой x положение меняется достаточно, чтобы вызвать такое поведение.
  3. Связывание на втором Observable не всегда срабатывает (это ненадежно). Я думаю, что здесь может быть состояние гонки или что-то еще, потому что иногда я обновляю страницу и получаю перетаскивание один раз (из #1), а в других случаях я не получу его вообще.

сс

Обратите внимание, что сначала после чистого обновления я не могу перетащить маркер (#3), затем я обновляюсь, и я не могу перетащить маркер за настройку границ из первого Observable- и черная полоса изменения размера исчезает и появляется снова как координата x моей мыши входит и покидает этот конверт (#2).


Я уже давно бьюсь об этом, и был бы очень признателен за понимание того, что я делаю здесь неправильно. Короче хочу

  • первая наблюдаемая для "паузы" при перетаскивании, затем возобновление после перетаскивания
  • вторая наблюдаемая, чтобы не "завершить" (или "перезапустить") после завершения перетаскивания
  • вторая наблюдаемая надежно работает

Как я упоминал ранее, в настоящее время у меня есть эта логическая настройка в компоненте React componentDidUpdate Метод жизненного цикла, форма которого выглядит примерно так:

componentWillUpdate() {
  // bail if we don't have the ref to our table
  if (!tableEl) {
    return;
  }

  // try not to have a new Observable defined on each component update
  if (!this.resizerDrag$ && this.resizer) {
    this.resizerDrag$ = // second Observable from above
  }

  // try not to have a new Observable defined on each component update
  if (!this.resizerPos$) {
    this.resizerPos$ = // first Observable from above
  }
}

1 ответ

Я немного поиграл с этим сейчас, я не думаю, что этот ответ будет полным, но я хотел бы поделиться своими соображениями. Надеемся, что в игру вступит более продвинутый ум RxJS, и мы все вместе сможем это выяснить:).

Я воссоздала "облегченную" версию этого в CodePen, используя некоторые легкие манипуляции с jQuery, а не React. Вот что у меня так далеко:

"первая наблюдаемая, которая" делает паузу ", когда я перетаскиваю, затем возобновляет, когда я закончу перетаскивание"

Решение первого пункта помогает с двумя другими. На основании того, что я должен был сделать, чтобы получить resizerElЯ чувствую, что это отображается в render метод компонента, основанного на чем-то в this.state, Если это правда, это означает, что когда первая наблюдаемая все еще имеет способность создавать и уничтожать resizerEl даже в то время как вторая наблюдаемая слушает. Это означает, что resizerEl больше не сможет генерировать какие-либо события, даже если наблюдаемое не завершится, пока вы не наведете курсор.

В моем случае я заметил, что если вы перемещаете мышь достаточно быстро, чтобы выйти за пределы ширины того, что вы пытаетесь перетащить, это устранит resizerElЭто то, что мы хотим, но не в то время как мы пытаемся что-то перетащить!

Мое решение: я ввел еще одну переменную в "состояние" "компонента". Это установит true когда мы коснулись resizerEl, а потом false когда мы снова намылились

Тогда мы используем switchMap,

  Rx.DOM.mousemove(tableEl)
  .switchMap(function(event) {
    return this.state.mouseIsDown ? Rx.Observable.never() : Rx.Observable.of(event);
  })
  .map(...

Там, вероятно, лучший способ сделать это, чем просто придерживаться event назад в Обсерватории, но это была последняя часть, над которой я работал, и мой мозг отчасти жарен, хе-хе. Ключ здесь переключается на Observable.never пока мышь не нажата, мы не пойдем дальше по цепочке операторов.

На самом деле, одна хорошая вещь заключается в том, что это, возможно, даже не нужно помещать в this.state, так как это приведет к повторной визуализации. Вероятно, вы можете просто использовать переменную экземпляра, поскольку переменная важна только для функциональности Observables, а не для какой-либо визуализации. Итак, используя this.mouseIsDown было бы так же хорошо.

Как мы обращаемся с мышью вниз или вверх?

Часть 1:

...
Rx.DOM.mousedown(resizerEl)
  .do(() => this.mouseIsDown = true)

Лучше, конечно, абстрагироваться от этой функции, но это суть того, что она делает.

Часть 2:

...
return Rx.DOM.mousemove(tableEl)
  .map(mm => mm.x - md.x)
  .takeUntil(Rx.DOM.mouseup(document))
  .doOnCompleted(() => this.mouseIsDown = false)

Здесь мы пользуемся doOnComplete выполнить этот побочный эффект после завершения наблюдаемой, которая в этом случае будет на mouseup,

"вторая наблюдаемая, чтобы не" завершить " (или" перезапустить ") после завершения перетаскивания"

Теперь вот хитрый, я никогда не сталкивался с этой проблемой. Видишь ли, каждый раз Rx.DOM.mousedown(resizerEl) испускает событие, внутри flatMapновый Observable создается каждый раз с return Rx.DOM.mousemove(tableEl)..., Я использовал RxJS 4.1, когда делал это, так что возможно, что могут быть различия в поведении, но я обнаружил, что только потому, что внутренняя наблюдаемая завершена, не означает, что внешняя завершится также.

Так что же может происходить? Ну, я думаю, что так как вы используете React, это resizerEl создается / уничтожается соответственно при рендеринге компонента. Конечно, я не видел остальную часть вашего кода, но, пожалуйста, поправьте меня, если я ошибаюсь в этом предположении.

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

Итак, важный вопрос: как это resizerEl определяется и используется в вашем компоненте? Я предполагаю, что фактическая ссылка на это сделана с использованием, ну, ref, Но если этот элемент DOM когда-либо разрушается или воссоздается, то Rx.Dom переплет необходимо повторять снова и снова.

Я вижу, вы делаете это с componentDidUpdate, Тем не менее Rx.Dom.mousedown событие может быть связано со старой копией ссылки на resizerEl, Даже если компонент уничтожает ресайзер в DOM и устанавливает ref (Я предполагаю, что это this.resizer) чтобы null или же undefined, это не разрушает наблюдаемое, связанное с этим элементом. На самом деле, я даже не думаю, что он удаляет его из памяти, даже если он удален из DOM! Это означает, что this.resizerDrag$ никогда не оценю false/nullи он все еще будет прослушивать элемент, которого больше нет в DOM.

Если это так, то как-то так в componentWillUpdate может помочь:

if (!this.resizerDrag$ && this.resizer) {
    this.resizerDrag$ = // second Observable from above
  }
  else if (!this.resizer && this.resizerDrag$) {
    this.resizerDrag$ = null;
  }

Это удалит Observable, если объект resizer перестанет существовать, таким образом мы сможем правильно инициализировать его по возвращении. Есть лучший способ сделать с Subjectsсохраняя подписку на одну тему и просто подписывая тему на разные потоки mousedown, как только они станут доступны, но давайте сделаем это просто:).

Это то, что нам нужно, чтобы остальная часть вашего кода (для этого компонента) рассказала, что происходит, и выяснила, как ее решить. Но моя гипотеза состоит в том, что вам нужно намеренно уничтожить наблюдаемое, если this.resizer когда-либо удаляется.

вторая наблюдаемая надежно работает

Уверен, что как только вышеперечисленные проблемы сработают, эта проблема исчезнет. Легко и приятно!

CodePen

Вот очень наивный макет, который я сделал для этой проблемы: https://codepen.io/anon/pen/KmapYZ

Перетащите синие круги назад и вперед вдоль оси X. (Имеет некоторые небольшие проблемы и ошибки, не связанные с объемом этого вопроса, поэтому я не беспокоюсь о них.)

Конечно, я сделал несколько небольших изменений, чтобы не отставать от более тупого подхода, который я использовал. Но все концепции, как и большая часть написанного вами кода, изменены, чтобы соответствовать этому подходу.

Как я упоминал ранее, я не сталкивался с проблемой перетаскивания, работающей только один раз, так что это лучше демонстрирует решение для приостановки первого Observable. Я повторно использую элемент перетаскивания, поэтому я не столкнулся с проблемой "перетаскивания только один раз".

Я надеюсь, что вы или кто-либо еще можете прокомментировать некоторые улучшения этого подхода или просто показать нам лучший (потенциально более идиоматический) подход.

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