Javascript 基于另一个可观测的自定义过滤器的函数反应算子
为了好玩和学习,我尝试在我的应用程序中使用函数式反应式编程实现一个撤销系统。我有一个状态更改流,需要保存到撤消堆栈中。当用户单击undo时,我从堆栈中获取一个值,并相应地更新应用程序状态 问题在于,此更新本身也会在状态更改流中生成一个事件。所以我想从状态更改中派生另一个流,它的状态在撤销后立即更改 一个简单的图表:Javascript 基于另一个可观测的自定义过滤器的函数反应算子,javascript,reactive-programming,rxjs,bacon.js,Javascript,Reactive Programming,Rxjs,Bacon.js,为了好玩和学习,我尝试在我的应用程序中使用函数式反应式编程实现一个撤销系统。我有一个状态更改流,需要保存到撤消堆栈中。当用户单击undo时,我从堆栈中获取一个值,并相应地更新应用程序状态 问题在于,此更新本身也会在状态更改流中生成一个事件。所以我想从状态更改中派生另一个流,它的状态在撤销后立即更改 一个简单的图表: states ----S----S----S---- undos -------U----------- save ----S---------S----
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');
看这个。诀窍是merge是一个垂直组合器(相对于流图是垂直的),而scan是一个水平组合器。使用这两种方法,首先合并,然后扫描,您可以构建更强大的组合器,就像解决问题的组合器一样。Bacon和Kefir中的解决方案类似。对于Bacon.js,我将使用函数来维护整个撤销堆栈的属性。这样,我们可以支持多个连续的撤消步骤
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()
但你所在的州看起来会有所不同。如果您向更新添加索引,您将注意到
S1-U1-S0-S2
vsS1-S2
->即,当您执行撤销1(U1)时,您的状态将有效地更改为状态0(未显示在流#3中),因此您必须在该位置更改该状态,或者显示状态0,并从流#3中删除状态1希望你明白我的意思。我不知道你的意思。请不要过多地讨论撤销功能的逻辑,我主要是在上下文中偶然发现了一个我无法使用流解决的问题。现在,我对撤销方面不太感兴趣,而是对这个特定流场景的一般解决方案感兴趣。如果您想合并两个流,请考虑合并两个数组,即:[{a:1},{a:2},{x:!0},{a:3},{a:4},{a:5}]。reduce(函数(a,v){if(v.x){a.pop()}else a.push(v);返回a;[])>yourMergedStream.reduce(函数removePreviousUndo(a,v){/*…*/})
请参阅:比我想象的更简单。我尝试过类似的东西,不知道为什么失败了,但后来我开始发明一些疯狂复杂的东西:-)。