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

77
8

ОБНОВИТЬ

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

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

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

Кроме того, я использую RxJS v5 и последнюю версию React.

Все еще получаю зависание RxJS...

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

Грубо говоря, первый выглядит так (все ниже определено в методе жизненного цикла 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 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 изменится настолько, чтобы вызвать это поведение. Связывание со вторым наблюдаемым не всегда срабатывает (оно ненадежно). Я думаю, что может быть состояние гонки или что-то происходит здесь, потому что иногда я обновляю страницу, и я получу перетаскивание один раз (из # 1), а в других случаях я не получу его вообще.

ss

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

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

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

Как я уже упоминал ранее, в настоящее время у меня есть эта логическая установка в методе жизненного цикла компонента componentDidUpdate 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
}
}

спросил(а) 2021-01-25T16:11:38+03:00 5 месяцев назад
1
Решение
77

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

Я воссоздал версию Lite-er в CodePen, используя небольшую манипуляцию jQuery, а не React. Вот что у меня есть до сих пор:

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

Решение первой точки помогает с двумя другими. Основываясь на том, что мне нужно было сделать, чтобы получить мой resizerEl, у меня 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, но это была последняя его часть, над которой я работал, и мой мозг - это жареный хе-хе. Ключевым моментом здесь является переход на 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 создается/уничтожается соответственно, когда компонент выполняет рендеринг. Конечно, я не видел остальную часть вашего кода, но, пожалуйста, поправьте меня, если я ошибаюсь в этом предположении.

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

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

Я вижу, что вы делаете это с помощью componentDidUpdate. Однако событие Rx.Dom.mousedown все еще может быть привязано к старой копии ref для resizerEl. Даже если компонент уничтожает resizer в DOM и устанавливает ref (я предполагаю, что this.resizer) в null или undefined, что не разрушает Observable, привязанный к этому элементу. На самом деле, я даже не думаю, что он удаляет его из памяти, даже если он удаляется из 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 потоки, как только они станут доступны, но пусть это будет просто :).

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

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

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

CodePen

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

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

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

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

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

ответил(а) 2021-01-25T16:11:38+03:00 5 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема