RxJS DOM暂停可观察而另一个"正在拖动"?

时间:2017-04-26 16:08:06

标签: reactjs rxjs rxjs-dom

更新

我已尝试在此处制作独立版本: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 */)

这很有效,并且给了我这个:

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仍处于活动状态,因此一旦我的x位置变化足以触发该行为,我的黑色div就会消失。
  3. 第二个Observable上的绑定似乎总是触发(它不可靠)。我认为这里可能存在竞争条件或某些事情,因为有时候我会刷新页面并且我将获得拖动一次(来自#1),有时我赢了& #39; t得到它。
  4. ss

    首先注意清洁刷新后我不能拖动手柄(#3),然后刷新,我无法将手柄拖过第一个Observable的边界设置 - 并且黑色缩放器栏消失并重新出现,因为我的鼠标坐标进入并离开该信封(#2)。

    我已经在这方面做了很长一段时间了,并且非常感谢我在这里做错了什么。简而言之,我想要

    • 第一个Observable到"暂停"当我拖动时,然后在我完成拖动时恢复
    • 第二个Observable没有"完成" (或者#34;重启")拖动完成后
    • 第二个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
      }
    }
    

1 个答案:

答案 0 :(得分:0)

我现在已经玩过这个了,我不认为这个答案会完整,但我想分享我的见解。希望一个更先进的RxJS思想会引起人们的注意,我们都可以共同努力解决这个问题:)。

我重新创造了一个" lite-er"在CodePen中的这个版本,使用一些轻微的jQuery操作而不是React。这就是我到目前为止所拥有的:

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

解决第一点有助于其他两点。根据我必须做的事情来获取resizerEl,我感觉它是基于render中的某些内容在组件的this.state方法中呈现的。如果这是真的,那意味着当第一个observable仍然能够创建和销毁resizerEl时,即使第二个observable正在侦听。这意味着resizerEl将无法再生成任何事件,即使观察结果没有完成,直到您被篡改为止。

在我的情况下,我注意到如果你移动鼠标足够快以超出你想要拖动的宽度,它将消除resizerEl,这是我们想要的,但不是我们和#39;试图拖东西!

我的解决方案:我在"州"中引入了另一个变量。 "组件"。当我们在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)

在observable完成后,我们利用doOnComplete来执行此副作用,在这种情况下,它将在mouseup上。

"第二个Observable没有"完成" (或"重启")一旦拖动完成"

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

那么可以发生什么?好吧,我想,既然你正在使用React,那么当组件呈现时,resizerEl分别被创建/销毁。我当然没有看到你的其余代码,但请纠正我如果我对这个假设有误。

这对我来说不是问题,因为为了简单起见,我只是重复使用与拖动器相同的元素,只是在我没有悬停在可拖动元素上时隐藏它。

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

我看到您正在使用Rx.Dom.mousedown执行此操作。但是,resizerEl事件仍可能绑定到引用ref的旧副本。即使组件破坏DOM中的resizer,并将this.resizer(我假设为null)设置为undefinedthis.resizerDrag$,也不会破坏Observable与该元素绑定。事实上,我甚至不认为它会从内存中删除它,即使它已经从DOM中删除了!这意味着false永远不会评估为null / componentWillUpdate,并且它仍然会监听不再位于DOM中的元素。

如果是这种情况,if (!this.resizerDrag$ && this.resizer) { this.resizerDrag$ = // second Observable from above } else if (!this.resizer && this.resizerDrag$) { this.resizerDrag$ = null; } 中的类似内容可能有所帮助:

Subjects

如果resizer对象不再存在,这将删除Observable,这样我们可以在它返回时正确地重新初始化它。最好处理this.resizer,保留对一个主题的订阅,只要将主题订阅到不同的mousedown流一旦可用,但让我们保持这个简单:)。 / p>

这是我们必须看到你的代码的其余部分(对于这个组件)告诉你发生了什么,并想出如何解决它。但是,我的假设是,如果{{1}}被删除,你需要故意破坏Observable。

第二个Observable可靠地工作

很明白,一旦上述两个问题奏效,这个问题就会消失。很好,很容易!

CodePen

这是我对这个问题做出的非常天真的模型: https://codepen.io/anon/pen/KmapYZ

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

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

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

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