0

更新

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

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

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

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


仍然掌握RxJS的窍门......

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

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

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. mousemove我拖动时,来自第一个可观察对象的仍然处于活动状态,因此一旦我的x位置变化足以触发该行为,我的黑色 div 就会消失。
  3. 第二个 Observable 上的绑定似乎并不总是触发(它不可靠)。我认为这里可能存在竞争条件或发生的事情,因为有时我会刷新页面并且我会得到一次拖动(从#1),而其他时候我根本不会得到它。

ss

请注意,在干净刷新后,我无法拖动句柄(#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
  }
}
4

1 回答 1

0

我现在已经玩了一点,我认为这个答案并不完整,但我想分享我的见解。希望一个更高级的 RxJS 头脑会加入进来,我们可以一起努力解决这个问题:)。

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

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

解决第一点有助于其他两点。根据我必须做的事情resizerEl,我感觉它是在render基于this.state. 如果这是真的,那意味着当第一个 observable 仍然具有创建和销毁的能力时,resizerEl即使第二个 observable 正在侦听。这意味着resizerEl将不再能够生成任何事件,即使 observable 直到您将鼠标悬停才完成。

就我而言,我注意到,如果您将鼠标移动得足够快以超出您尝试拖动的宽度,它将消除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在可观察对象完成后执行此副作用,在本例中为 on mouseup

“一旦拖动完成,第二个 Observable 不会“完成”(或“重新启动”)”

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

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

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

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

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

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

第二个可靠工作的 Observable

可以肯定的是,一旦上述两个问题起作用,这个问题就会消失。好,易于!

代码笔

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

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

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

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

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

于 2017-04-26T19:01:34.317 回答