2

我正在尝试了解 bacon.js 和 FRP,因此尝试制作一个简单的拖放示例,但是我在懒惰地评估一段代码时遇到了麻烦。当我将 a 添加.log()到流中时,它看起来看起来和行为都很好,但是如果我把它拿出来,它就不会更新。这是我正在做的事情:

// UI streams
block_mousedown  = block_el.asEventStream('mousedown').map(xyFromEvent);
global_mousemove = html.asEventStream('mousemove').map(xyFromEvent);
global_mouseup   = html.asEventStream('mouseup');

// Composites
isDragging    = block_mousedown.merge(global_mouseup.map(0));
mouseDragging = Bacon.combineAsArray(isDragging, global_mousemove)
    .filter(function(v){ return notZero(v[0]) })

mouseDeltaFromClick = mouseDragging
    .map(getDelta)

// Block offset when it was clicked on
block_pos_at_mousedown = block_mousedown
    .map( function(a,b){ return block_el.offset();})
    .map(function(e){ return [e.left, e.top]; })
    // If I remove this log(), it doesn't evaluate
    .log();

// merge mouse delta with block position when clicked    
mouseDeltaAndBlockPos = mouseDeltaFromClick
    .combine(block_pos_at_mousedown, '.concat')
    .onValue( function(e){
        block_el.css({
            top  : e[3]+e[1]+"px",
            left : e[2]+e[0]+"px"
        });
    });    

这是它的一个jsFiddle

我在想我可能做错了,这甚至是正确的方法吗?我想在单击块时通过块的位置,该位置应该更新mousedown但不与mousemove.

4

1 回答 1

2

您描述的行为与惰性评估无关:问题的根源是执行顺序。

在您的代码中(没有log()on block_pos_at_mousedownmouseDeltaFromClick之前似乎发生了变化block_pos_at_mousedown(我必须说我不知道​​如何准确地log()更改顺序)。让我们坚持下去。

observable.combine方法期望Property作为第一个参数 -EventStream您传递的将自动转换。现在,mouseDeltaAndBlockPos无论何时更改(并因此触发所有回调)mouseDeltaFromClick block_pos_at_mousedown更改。

因此,当在代码末尾的回调mouseDeltaFromClick之前触发时,会使用新的增量但使用旧的块位置(因为已转换为)调用。旧值是这样块在每次单击时都会捕捉到左上角。block_pos_at_mousedownback_pos_at_mousedownProperty[0,0]

如何解决?安全的方法是对不相关回调的执行顺序不做任何假设,并牢记这一点再次编写它。我想出了这个:

function xyFromEvent(v){ return [v.clientX, v.clientY]; }

function getDelta(t){
    var a = t[1];
    var b = t[0];
    return [a[0]-b[0], a[1]-b[1]]; 
}

function add(p1, p2) {
    return [p1[0] + p2[0], p1[1] + p2[1]];   
}

$().ready(function () {
    var block = $("#clickable-block");
    var html  = $("html");

    var blockDragging = block.asEventStream('mousedown').map(true)
                             .merge(html.asEventStream('mouseup').map(false))
                             .toProperty(false);

    var deltas = html.asEventStream('mousemove').map(xyFromEvent).slidingWindow(2,2).map(getDelta);

    // Just like deltas, but [0,0] when user is not dragging the box.
    var draggingDeltas = Bacon.combineWith(function(delta, dragging) {
        if(!dragging) {
            return [0, 0];
        }
        return delta;
    }, deltas, blockDragging);

    var blockPosition = draggingDeltas.scan([0,0], add);

    blockPosition.onValue(function(pos) {
        block.css({
            top  : pos[1] + "px",
            left : pos[0] + "px"
        });
    });
});

和 jsFiddle:http: //jsfiddle.net/aknNh/25/

编辑:在评论raimohanska建议使用另一种解决方案:http flatMap: //jsfiddle.net/TFPge/1/

于 2013-06-23T22:36:29.510 回答