【问题标题】:Creating an observable 'completed' event in RxJS在 RxJS 中创建一个可观察的“完成”事件
【发布时间】:2014-06-13 21:45:51
【问题描述】:

鉴于:the reactive extensions drag and drop example,您将如何仅订阅 drop 事件?

我已修改代码以订阅“已完成”回调,但未完成。

    (function (global) {

    function main () {
        var dragTarget = document.getElementById('dragTarget');
        var $dragTarget = $(dragTarget);

        // 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
            .filter(function(md){                   
                //console.log(md.offsetX + ", " + md.offsetY);
                return  md.offsetX <= 100
                        ||
                        md.offsetY <= 100;
            })
            .flatMap(function (md) {

                // calculate offsets when mouse down
                var startX = md.offsetX, startY = md.offsetY;

                // Calculate delta with mousemove until mouseup
                return mousemove.map(function (mm) {
                    mm.preventDefault();

                    return {
                        left: mm.clientX - startX,
                        top: mm.clientY - startY
                    };
                }).takeUntil(mouseup);
            });


        // Update position
        var subscription = mousedrag.subscribe(
        function (pos) {                    
            dragTarget.style.top = pos.top + 'px';
            dragTarget.style.left = pos.left + 'px';
        },
        function(errorToIgnore) {},
        function() {    alert('drop');});

    }

    main();

}(window));

我读过热的可观察对象,例如从鼠标事件创建的对象,永远不会“完成”。它是否正确?我怎样才能在“drop”时获得回调?

【问题讨论】:

    标签: javascript reactive-programming rxjs


    【解决方案1】:

    这样的事情应该可以解决问题。

    (function (global) {
    
        function main () {
            var dragTarget = document.getElementById('dragTarget');
    
            // 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 drop = mousedown
                    .selectMany(
                        Rx.Observable
                            .concat(
                                [
                                    mousemove.take(1).ignoreElements(),
                                    mouseup.take(1)
                                ]
                            )
                    );
        }
    
        main();
    
    }(window));
    

    编辑:

    如果您将 observable 视为一个异步函数,它产生多个值,然后可能完成或出错,您会立即认识到只能有一个完成事件。

    当您开始组合多个函数时,最外层的函数仍然只完成一次,即使该函数内部包含多个函数。所以即使“完成”的总数是 3,最外层的函数仍然只完成一次。

    基本上,这意味着如果假设最外层的函数在每次拖动完成时都返回一个值,那么您需要一种实际执行此操作的方法。您需要将拖动完成转换为最外层 observable 的“onNext”事件。

    任何你能做到的方式都会得到你想要的。也许这是最外层函数返回的唯一类型的事件,或者它也可能返回拖动开始和移动,但只要它返回拖动完成,你就会得到你需要的(即使你必须稍后过滤)。

    我上面给出的示例只是在最外层 observable 中返回拖放的一种方式。

    【讨论】:

    • 嗯,这行得通。非常感谢。现在我只需要弄清楚为什么!我希望有一个更优雅的解决方案来强制 observable 在 .takeUntil(mouseup); 上“完成”;
    • 感谢您的解释!让我大跌眼镜的是构图顺序的重要性。公平地说,对于大多数函数式编程,顺序并不重要吗?我认为,来自一个相当幼稚的 linq 背景,我希望 Rx 具有与 SQL 相同的“顺序独立性”。如果有人有任何有关此问题的来源的链接,我将不胜感激!
    • 顺序在 SQL 中绝对很重要。如果反转左连接的查询会发生什么?你要么得到更多的结果,要么得到更少的结果。在 SQL 的很多情况下,您只是在进行过滤。像这样的查询可以是独立的(除非你排序和限制)。有些事情是,有些事情不是顺序无关的(order-by 和 join 只是两个例子)。 LINQ to Entities 对此规则没有例外,Rx 也不例外。 :) 话虽如此,我确信有些东西 Rx 可以表达而 SQL 不能。
    • SQL 查询类比在描述诸如此类的内容时效果不佳。相反,请尝试将 Observable 视为具有多个返回值的异步函数。一个函数可以被调用(订阅),产生一个值(onNext),抛出一个异常(onError)和完成(onCompleted)。
    • take 很明显,因为我们只是在得到一个项目后停止收听。另一方面,ignoreElements 不仅仅是take(0),而是让 observable 完成,然后它本身完成,但它不返回任何值。所以,mousemove.take(1).ignoreElements() 是一个 observable,它只会在一次鼠标移动后完成。
    【解决方案2】:

    OP 的官方示例链接失效了,在这里:

    https://github.com/Reactive-Extensions/RxJS/blob/master/examples/dragndrop/dragndrop.js

    原始问题有两种解决方案,使用在官方示例上扩展的原始鼠标事件,或使用原生 HTML5 拖放事件。

    由“mouseup”、“mousedown”和“mousemove”组成

    首先我们使用mousedown1.switchMapTo(mousemove.takeUntil(mouseup).take(1)) 来获取“拖动开始”流。 switchMapTo (see doc) 使用扁平化组合(将“mousedown1”映射到“鼠标移动直到鼠标向上”的第一次发射,也称为“拖动”)和switchsee doc),给了我们最新的mousedown1 发出然后拖动,即不仅仅是在您在框上单击鼠标时。

    另一个“mousedown”流mousedown2 用于组成mousedrag 流,因此我们可以在拖动时不断地渲染框。上面的mousedown1 优先于mousedown2。请参阅https://stackoverflow.com/a/35964479/232288 了解更多信息。

    一旦我们有了“拖动开始”流,我们就可以通过:mousedragstart.mergeMapTo(mouseup.take(1)) 获得“拖动停止”流。 mergeMapTo (see doc) 将 'mousedragstart' 映射到 'mouseup' 的每个第一次发射,为我们提供最新的 'mousedragstart' 然后是 'mouseup',本质上是 'drag stop'。

    下面的演示适用于 RxJS v5:

    const { fromEvent } = Rx.Observable;
    const target = document.querySelector('.box');
    const events = document.querySelector('#events');
    
    const mouseup = fromEvent(target, 'mouseup');
    const mousemove = fromEvent(document, 'mousemove');
    const [mousedown1, mousedown2] = prioritisingClone(fromEvent(target, 'mousedown'));
    
    const mousedrag = mousedown2.mergeMap((e) => {
    
      const startX = e.clientX + window.scrollX;
      const startY = e.clientY + window.scrollY;
      const startLeft = parseInt(e.target.style.left, 10) || 0;
      const startTop = parseInt(e.target.style.top, 10) || 0;
    
      return mousemove.map((e2) => {
        e2.preventDefault();
    
        return {
          left: startLeft + e2.clientX - startX,
          top: startTop + e2.clientY - startY
        };
      }).takeUntil(mouseup);
    });
    
    // map the latest mouse down emit to the first mouse drag emit, i.e. emits after pressing down and
    // then dragging.
    const mousedragstart = mousedown1.switchMapTo(mousemove.takeUntil(mouseup).take(1));
    
    // map the mouse drag start stream to first emit of a mouse up stream, i.e. emits after dragging and
    // then releasing mouse button.
    const mousedragstop = mousedragstart.mergeMapTo(mouseup.take(1));
    
    mousedrag.subscribe((pos) => {
      target.style.top = pos.top + 'px';
      target.style.left = pos.left + 'px';
    });
    
    mousedragstart.subscribe(() => {
      console.log('Dragging started');
      events.innerText = 'Dragging started';
    });
    
    mousedragstop.subscribe(() => {
      console.log('Dragging stopped');
      events.innerText = 'Dragging stopped';
    });
    
    function prioritisingClone(stream$) {
      const first = new Rx.Subject();
      const second = stream$.do(x => first.next(x)).share();
    
      return [
        Rx.Observable.using(
          () => second.subscribe(() => {}),
          () => first
        ),
        second,
      ];
    }
    .box {
      position: relative;
      width: 150px;
      height: 150px;
      background: seagreen;
      cursor: pointer;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.3/Rx.js"></script>
    <div class="box"></div>
    
    <h3 id="events"></h3>

    由 HTML5 原生 'dragstart'、'dragover' 和 'drop' 组成

    本机事件使事情变得更容易一些。只需确保 dragover 观察者调用 e.preventDefault() 以便 body 元素可以成为有效的放置区域。 See more info.

    我们使用switchMap (see doc) 的方式与上面使用switchMapTo 的方式类似:展平最新的“dragstart”并将其映射到最新的“drop”以获得我们的“drag then drop”流。

    不同的是,只有当用户放下 div 时,我们才会更新位置。

    const { fromEvent } = Rx.Observable;
    const target = document.querySelector('.box');
    const events = document.querySelector('#events');
    
    const dragstart = fromEvent(target, 'dragstart');
    const dragover = fromEvent(document.body, 'dragover');
    const drop = fromEvent(document.body, 'drop');
    
    const dragthendrop = dragstart.switchMap((e) => {
      const startX = e.clientX + window.scrollX;
      const startY = e.clientY + window.scrollY;
      const startLeft = parseInt(e.target.style.left, 10) || 0;
      const startTop = parseInt(e.target.style.top, 10) || 0;
      // set dataTransfer for Firefox
      e.dataTransfer.setData('text/html', null);
      console.log('Dragging started');
      events.innerText = 'Dragging started';
    
      return drop
        .take(1)
        .map((e2) => {
          return {
            left: startLeft + e2.clientX - startX,
            top: startTop + e2.clientY - startY
          };
        });
    });
    
    dragover.subscribe((e) => {
      // make it accepting drop events
      e.preventDefault();
    });
    
    dragthendrop.subscribe((pos) => {
      target.style.top = `${pos.top}px`;
      target.style.left = `${pos.left}px`;
      console.log('Dragging stopped');
      events.innerText = 'Dragging stopped';
    });
    .box {
      position: relative;
      width: 150px;
      height: 150px;
      background: seagreen;
      cursor: pointer;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.3/Rx.js"></script>
    <div class="box" draggable="true"></div>
    
    <h3 id="events"></h3>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-24
      • 1970-01-01
      • 2022-01-23
      • 1970-01-01
      • 1970-01-01
      • 2021-12-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多