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 */)
Это прекрасно работает, и дает мне это:
Итак, теперь я хочу предоставить фактическое поведение "перетаскивания", и я попытался настроить новый 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 */)
Есть три проблемы с этим:
- Как только я совершил свое первое "перетаскивание", я больше не могу. Я понимаю, что
takeUntil
завершает Observable, и я не уверен, как я могу "перезапустить" его. mousemove
с первой наблюдаемой все еще активен, пока я тащу, поэтому мой черный div исчезнет, как только мойx
положение меняется достаточно, чтобы вызвать такое поведение.- Связывание на втором 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. Я повторно использую элемент перетаскивания, поэтому я не столкнулся с проблемой "перетаскивания только один раз".
Я надеюсь, что вы или кто-либо еще можете прокомментировать некоторые улучшения этого подхода или просто показать нам лучший (потенциально более идиоматический) подход.