基于另一个 Observable 的自定义过滤器的功能反应运算符

Functional reactive operator for custom filter based on another Observable

为了好玩和学习,我正在尝试使用函数式反应式编程在我的应用程序中实现撤消系统。我有一个状态更改流,需要将其保存到撤消堆栈中。当用户单击撤消时,我从堆栈中获取一个值并相应地更新应用程序状态。

问题在于此更新本身也会在状态更改流中生成一个事件。所以我想要的是从状态更改中派生另一个流,它会在撤消后立即更改状态。

一个简单的图表:

states   ----S----S----S----
undos    -------U-----------     
save     ----S---------S----

第一行是应用程序状态变化的流,第二行是用户触发的撤消事件,第三行是我想实现和监听的流,而不是第一个流。

在 FRP 中表达这种意图的最佳方式是什么?

在 RxJS 中:

var state = Rx.Observable.interval(2000)
  .map(s => ({type:'s', value: s}))
  .take(3);
var undo = Rx.Observable.interval(3000)
  .map(u => ({type:'u', value: u}; }))
  .take(1);

var save = state.merge(undo).scan((prev, curr) =>
  if (prev.type === 'u') {
    return {type: 'removed'};
  } else {
    return curr;
  }
}).filter(x => x.type !== 'removed' && x.type !== 'u');

看到这个 JSBin。诀窍是 merge 是一个垂直组合器(垂直于流图),而 scan 是一个水平组合器。使用两者,先合并,然后扫描,您可以构建更强大的组合器,例如解决您问题的组合器。培根和开菲尔的解决方案类似。

使用 Bacon.js,我会使用 Bacon.update 函数来维护 属性 整个撤消堆栈。这样,我们就可以支持多个连续的撤消步骤。

var state = Bacon.sequentially(1000, [1,2,3])
var undo = Bacon.later(2500, "undo")

var stateStack = Bacon.update(
[],
state, function(prev, s) { return prev.concat(s) },
undo, function(prev, u) { return prev.slice(0, prev.length - 1) }
)

var currentState = stateStack.changes().map(function(stack) {
  return stack[stack.length-1]
})

stateStack.log()
currentState.log()

http://jsbin.com/boharo/2/edit?js,console