【问题标题】:RxJS DOM pause observable while another "is dragging"?RxJS DOM 暂停可观察,而另一个“正在拖动”?
【发布时间】:2017-09-24 03:55:43
【问题描述】:

更新

我尝试在这里制作一个独立版本:https://codepen.io/neezer/pen/pPRJar

它不像我的本地副本那样工作,但我希望它足够相似,你可以看到我想要去哪里。

我也没有得到完全相同的行为,因为我将侦听器目标更改为 document,这似乎对某些人有所帮助。

另外,我正在使用 RxJS v5 和最新版本的 React。


仍然掌握RxJS...

我有两个 Observable:一个订阅了鼠标悬停在表格上的 x 坐标以显示调整大小的列,另一个允许用户在该列上拖动。

粗略地说,第一个看起来像这样(以下所有内容都定义在 React 组件中的 componentDidUpdate 生命周期方法中):

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 */)

这样做存在三个问题:

  1. 一旦我完成了我的第一次“拖动”,我就不能再做任何事情了。我的理解是 takeUntil 完成了 Observable,我不确定如何“重新启动”它。
  2. 当我拖动时,来自第一个 observable 的 mousemove 仍然处于活动状态,因此一旦我的 x 位置变化足以触发该行为,我的黑色 div 就会消失。
  3. 第二个 Observable 上的绑定似乎并不总是触发(它不可靠)。我认为这里可能存在竞争条件或发生了什么事情,因为有时我会刷新页面并且我会得到拖动一次(来自 #1),而其他时候我不会得到它完全没有。

请注意,在干净刷新后,我无法拖动句柄(#3),然后我刷新,我无法将句柄拖动到第一个 Observable 的边界设置之外——黑色的大小调整栏消失了并在我的鼠标的 x 坐标进入和离开该信封时重新出现 (#2)。


我一直在研究这个问题已经有一段时间了,并且非常感谢任何关于我在这里做错了什么的见解。总之,我想要

  • 第一个 Observable 在我拖动时“暂停”,然后在我完成拖动时恢复
  • 拖动完成后第二个 Observable 不会“完成”(或“重新启动”)
  • 第二个可靠工作的 Observable

正如我之前提到的,我目前在 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
  }
}

【问题讨论】:

  • 是否有可能获得一个 jsfiddle 或 jsbin 最小限度地复制问题。我对可能是什么有一些想法,但如果不进行一些调试就很难说。我只想说我没有看到您的代码有任何明显错误,但是缺少一些部分。
  • @paulpdaniels 用一个例子更新了我的问题,尽管它不是我原始代码的一对一。我尽力复制它,而不需要在我的实际应用中进行所有动态计算。

标签: reactjs rxjs rxjs-dom


【解决方案1】:

我已经对此进行了一些尝试,我认为这个答案并不完整,但我想分享我的见解。希望更高级的 RxJS 思维能加入进来,我们可以一起努力解决这个问题:)。

我在 CodePen 中重新创建了一个“精简版”版本,使用一些轻量级的 jQuery 操作而不是 React。到目前为止,这是我所拥有的:

“第一个 Observable 在我拖动时“暂停”,然后在我完成拖动时恢复”

解决第一点有助于解决其他两个问题。根据我为获得resizerEl 所做的工作,我感觉它是基于this.state 中的某些内容在组件的render 方法中呈现的。如果这是真的,那意味着当第一个 observable 仍然有能力创建和销毁resizerEl 时,即使第二个 observable 正在监听。这意味着 resizerEl 将不再能够生成任何事件,即使 observable 在您将鼠标悬停之前不会完成。

在我的例子中,我注意到如果您将鼠标移动得足够快以超出您尝试拖动的宽度,它将消除resizerEl,这是我们想要的,但不是在我们想要的时候试图拖动东西!

我的解决方案:我在“组件”的“状态”中引入了另一个变量。当我们将鼠标放在resizerEl 上时,这将设置为true,然后当我们再次将鼠标放在上面时设置为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 上。

“拖拽完成后第二个 Observable 不会“完成”(或“重启”)

现在这是一个棘手的问题,我从来没有遇到过这个问题。你看,每次Rx.DOM.mousedown(resizerEl) 发出一个事件,在flatMap 内部,每次都会用return Rx.DOM.mousemove(tableEl)... 创建一个新的Observable。我在制作时使用了 RxJS 4.1,因此可能存在行为差异,但我发现仅仅因为内部 observable 完成并不意味着外部 observable 也会完成。

那么可能会发生什么?好吧,我在想,由于您使用的是 React,因此在渲染组件时分别创建/销毁了 resizerEl。当然,我还没有看到你的其余代码,但是如果我对这个假设有误,请纠正我

这对我来说不是问题,因为为了简单起见,我只是将相同的元素重新用作拖动器,仅当我没有将鼠标悬停在可拖动元素上时才隐藏它。

所以重要的问题是:resizerEl 是如何在您的组件中定义和使用的?我假设对它的实际引用是使用ref 进行的。但是,如果该 DOM 元素被销毁或重新创建,则需要重新重复 Rx.Dom 绑定。

我看到您正在使用 componentDidUpdate 执行此操作。但是,Rx.Dom.mousedown 事件可能仍会绑定到对resizerEl 的引用的旧副本。即使组件破坏了 DOM 中的调整大小,并将ref(我假设是this.resizer)设置为nullundefined,也不会破坏绑定到该元素的 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,这样我们就可以在它返回时正确地重新初始化它。 Subjects 有更好的处理方式,保持订阅一个主题,并在主题可用时订阅不同的 mousedown 流,但让我们保持简单:)。

这是我们必须看到你的其余代码(对于这个组件)告诉发生了什么,并弄清楚如何解决它的地方。但是,我的假设是,如果 this.resizer 被删除,您需要故意销毁 Observable。

第二个可靠工作的 Observable

可以肯定,一旦上述两个问题奏效,这个问题就会消失。又好又简单!

CodePen

这是我对这个问题所做的非常幼稚的模型: https://codepen.io/anon/pen/KmapYZ

沿 X 轴来回拖动蓝色圆圈。 (有一些与这个问题的范围无关的小问题和错误,所以我并不担心。)

我当然做了一些细微的改变,只是为了让它与我使用的更愚蠢的方法保持同步。但是所有的概念,以及您编写的大部分代码都在那里,经过修改以匹配这种方法。

正如我之前提到的,我没有遇到拖动只工作一次的问题,所以这更好地展示了暂停第一个 Observable 的解决方案。我重新使用了拖动元素,我认为这就是我没有遇到“仅拖动一次”问题的原因。

我希望您或其他任何人都可以对此方法进行一些改进发表评论,或者只是向我们展示一种更好(可能更惯用)的方法。

【讨论】:

  • 非常感谢@Amleonard 的详细回答!我会用一些细节更新我的问题,现在我忘记了,但我很想听到更多关于“有一个更好的方法来处理主题,保持订阅一个主题,只是订阅不同的主题mousedown 流一旦可用”...因为我正在探索与 BehaviorSubject 类似的东西
  • 用一个有点模仿我行为的 CodePen 更新了我的问题(一大堆我通常有动态值的硬代码)。
  • 我还要注意我是从 RxJS 文档中复制拖动示例:github.com/Reactive-Extensions/RxJS/blob/master/doc/…
  • @neezer 我只是在示例中遇到问题,因为悬停位置与行位置不匹配,呵呵。第一个会,直到它被拖动,第二个不会。
猜你喜欢
  • 2019-11-23
  • 2022-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-22
相关资源
最近更新 更多