使用 RxJS 进行响应式编程 - 这个滚动功能可以简化吗?

Reactive Programming with RxJS - can this scroll function be simplified?

我对反应式编程(和 RxJS)还很陌生,所有这些运算符都很难理解。

无论如何,我已经成功地编写了这个函数来处理拖动内容时文档的滚动。我现在想知道这是否可以简化。

基本上,onMouseDown 我需要每 10 毫秒检查一次鼠标的位置,当鼠标移动时我需要更新 clientY,这就是为什么我设置一个 Rx.Oberservable.interval(10) 并与 mouseMove 结合使用观察者。这将滚动页面,无论您是否移动鼠标(如预期的那样)。

代码如下:

handleWindowScrollOnDrag() {
    var dragTarget = this.getDOMNode()
    var scrollTarget = document.body
    var wHeight = window.innerHeight
    var maxScroll = document.documentElement.scrollHeight - wHeight

    // Get the three major events
    var mouseup   = Rx.Observable.fromEvent(document, 'mouseup');
    var mousemove = Rx.Observable.fromEvent(document, 'mousemove');
    var mousedown = Rx.Observable.fromEvent(dragTarget, 'mousedown');

    var mousedrag = mousedown.flatMap(function (md) {
        var y = scrollTarget.scrollTop
        var multiplier = 1

        // Scroll every 10ms until mouseup when we can
        var intervalSource = Rx.Observable.interval(10).takeUntil(mouseup);

        // Get actual clientY until mouseup
        var movement = mousemove.map(function (mm) {
            return {
                y: mm.clientY
            };
        }).takeUntil(mouseup);

        return Rx.Observable
            .combineLatest(movement, intervalSource, function (s1) {
                multiplier = 1

                if (s1.y < 100 && y >= 0) {
                    if (s1.y < 75) multiplier = 3;
                    if (s1.y < 50) multiplier = 5;
                    if (s1.y < 25) multiplier = 10;
                    if (s1.y < 15) multiplier = 20;
                    y -= (1 * multiplier)
                }

                if (s1.y > wHeight - 100 && y <= (maxScroll)) {
                    if (s1.y > wHeight - 75) multiplier = 3;
                    if (s1.y > wHeight - 50) multiplier = 5;
                    if (s1.y > wHeight - 25) multiplier = 10;
                    if (s1.y > wHeight - 15) multiplier = 20;
                    y += (1 * multiplier)
                }

                return {
                    y: y
                };
        });
    });

    // Update position
    this.subscription = mousedrag.subscribe(function (pos) {
        document.body.scrollTop = pos.y
    });

},

您可以删除额外的 takeUntils 您只需要 movement 流中的一个,因为 combineLatest 运算符将在完成时清理并处理所有基础订阅.

接下来,您可以通过使用 .scan 来管理累积状态而不是闭包变量来删除一些额外的状态。

您还可以通过简单地传递鼠标移动的 y 值来简化事件主体,而不是在 mapcombineLatest 运算符中产生对象分配的开销。

最后,我会改用 .withLatestFrom 而不是 combineLatest。由于您基本上已经以 100 fps 的间隔进行轮询 combineLatest 如果鼠标同时移动,发射速度会更快。

附带说明一下,虽然我不太熟悉 DOM 滚动的工作原理(而且我实际上并不知道您的代码在实际情况下的工作情况),但是向页面发送滚动值的速度比页面的实际呈现率似乎有点矫枉过正。降低间隔率并使用 jQuery.animate 之类的东西来平滑两者之间的滚动可能会更好。

;tldr

handleWindowScrollOnDrag() {
  var dragTarget = this.getDOMNode()
  var scrollTarget = document.body;
  var wHeight = window.innerHeight;
  var maxScroll = document.documentElement.scrollHeight - wHeight;

  // Get the three major events
  var mouseup   = Rx.Observable.fromEvent(document, 'mouseup');
  var mousemove = Rx.Observable.fromEvent(document, 'mousemove');
  var mousedown = Rx.Observable.fromEvent(dragTarget, 'mousedown');

  var mousedrag = mousedown.flatMap(function (md) {
      // Scroll every 10ms until mouseup when we can
      var intervalSource = Rx.Observable.interval(10, Rx.Scheduler.requestAnimationFrame);

      return intervalSource
          .takeUntil(mouseup)
          .withLatestFrom(mousemove, function (s1, s2) {
              return s2.clientY;
           })
           .scan(scrollTarget.scrollTop, function(y, delta) { 
              var multiplier = 1;

              if (delta  < 100 && y >= 0) {
                  if (delta < 75) multiplier = 3;
                  if (delta < 50) multiplier = 5;
                  if (delta < 25) multiplier = 10;
                  if (delta < 15) multiplier = 20;
                  y -= (1 * multiplier);
              }

              if (delta > wHeight - 100 && y <= (maxScroll)) {
                  if (delta > wHeight - 75) multiplier = 3;
                  if (delta > wHeight - 50) multiplier = 5;
                  if (delta > wHeight - 25) multiplier = 10;
                  if (delta > wHeight - 15) multiplier = 20;
                  y += (1 * multiplier);
              }

              return y;
        });
  });

  // Update position
  this.subscription = mousedrag.subscribe(function (pos) {
      document.body.scrollTop = pos;
  });

},

编辑

原文中的一个错误实际上可以进一步简化代码。您可以通过将 mousemove 传递给 withLatestFrom 并使用该结果选择器获取 [=26] 来一起删除 movement 流(以及对 map 的额外调用) =]

此外,如果您尝试将 interval 与页面的呈现循环同步,我还添加了您可能想要添加调度程序的位置,但我仍然会说您可能想要使用 15 毫秒(60 帧/秒)而不是 10 毫秒,它再次归结为滚动渲染的完成方式。

如果设置滚动位置只会在每个渲染循环结束时更新,那么它将正常工作。但是,如果它在每个 set 之后贪婪地重新计算,那么最好让像 React 这样的中介首先写入 Virtual DOM 而不是将所有更改直接刷新到 DOM.