作为一个练习,我试图建立2个依赖流哪个更新彼此。
测试应用程序是一个简单的“英寸< - >厘米”转换器,具有两个输入编辑。
我遇到的问题是,我不能让我怎么能停止递归,导致一个场变化。
为了更好地解释这个问题,让我们来看看代码的相关部分:
var cmValue = new Rx.BehaviorSubject(0),
inValue = new Rx.BehaviorSubject(0);
# handler #1
cmValue.distinctUntilChanged().subscribe(function(v) {
inValue.onNext(cmToIn(v));
});
# handler #2
inValue.distinctUntilChanged().subscribe(function (v) {
cmValue.onNext(inToCm(v));
});
因此,我们定义了对象的每一个保持当前的相应值。
现在想象一下,我们以英寸为单位的值更改为2
(使用inValue.onNext(2);
或通过键盘)。
接下来会发生什么 - 是处理程序#2被触发,它会调用厘米的值的相应重新计算。 这将导致以cmValue.onNext(0.7874015748031495)
这个呼叫实际上然后由处理程序#1处理,并引起英寸值(我们手动将一个)被重新计算,使用0.7874015748031495 * 2.54
式这引起另一个inValue.onNext(1.99999999999999973)
呼叫。
幸运的是 - 由于FP舍入误差这就是我们停下来。 但在其他情况下,这可能会导致更多的循环或甚至无限递归。
正如你所看到的-我部分地解决应用问题.distinctUntilChanged()
这至少让我们免受任何变化无限递归,但我们可以看到-在这种情况下,它并没有解决完全因为是不相等的问题(由于FP操作性质)。
所以,问题是:如何将一个实现一个通用的双向绑定不会引起自递归呢?
我强调通用要记,使用.select()
与舍入将是这一特定问题的部分解决方案,而不是通用的一个(我和其他人宁愿)。
完整的代码和演示: http://jsfiddle.net/ewr67eLr/
Answer 1:
在您的演示中,你有两个输入字段。 此输入“KEYUP”事件将是信息源,和输入值将是目的地。 在这种情况下,你不需要可变状态检查可观察的更新。
Rx.Observable.fromEvent(cmElement, 'keyup')
.map(targetValue)
.distinctUntilChanged()
.map(cmToIn)
.startWith(0)
.subscribe(function(v){ inElement.value = v; });
Rx.Observable.fromEvent(inElement, 'keyup')
.map(targetValue)
.distinctUntilChanged()
.map(inToCm)
.startWith(0)
.subscribe(function(v){ cmElement.value = v; });
这里检查我的例子: http://jsfiddle.net/537Lrcot/2/
Answer 2:
我有一个类似的任务来解决我的项目。
首先,你必须选择自己的立场真相 - 那就是要代表你的测量值,假设你选择厘米。
现在你只有一个是从多个源获取更新流工作。
由于有保存不能完全按照输入它表示的值,你必须比整个浮显著个数字输出。 一个人是不可能衡量英寸的11显著位的精度,所以没有点显示转换值到该精度。
function myRound(x, digits) {
var exp = Math.pow(10, digits);
return Math.round(x * exp) / exp;
}
cmValue.subscribe(function(v) {
if (document.activeElement !== cmElement) {
cmElement.value = myRound(v, 3).toFixed(3);
}
if (document.activeElement !== inElement) {
inElement.value = myRound(cmToIn(v), 3).toFixed(3);
}
});
到目前为止,这里的工作示例: http://jsfiddle.net/ewr67eLr/4/
剩下的是一个边缘的情况下,当我们改变我们的工作重点,以自动计算的值,然后该值重新计算我们用不同的数字第一个值。
这可以通过创建已经由用户改变的值流来解决:
cmInputValue = new Rx.BehaviorSubject(0),
inInputValue = new Rx.BehaviorSubject(0),
...
Rx.Observable.fromEvent(cmElement, 'input').subscribe(function (e) {
cmInputValue.onNext(e.target.value);
});
Rx.Observable.fromEvent(inElement, 'input').subscribe(function (e) {
inInputValue.onNext(e.target.value);
});
cmInputValue.distinctUntilChanged().subscribe(function (v) {
cmValue.onNext(v);
});
inInputValue.distinctUntilChanged().subscribe(function (v) {
cmValue.onNext(inToCm(v));
});
http://jsfiddle.net/ewr67eLr/6/
现在,这是我能解决这个任务的最佳方式。
Answer 3:
下面就来解决这个问题(即不依赖于DOM的具体状态)纯算法的方式。 基本上只使用一个变量来检测递归和中止更新。
/* two way binding */
var twowayBind = function (a, b, aToB, bToA) {
var updatingA = 0,
updatingB = 0,
subscribeA = new Rx.SingleAssignmentDisposable(),
subscribeB = new Rx.SingleAssignmentDisposable(),
subscriptions = new Rx.CompositeDisposable(subscribeA, subscribeB);
subscribeA.setDisposable(a.subscribe(function (value) {
if (!updatingB) {
++updatingA;
b.onNext(aToB(value));
--updatingA;
}
}));
subscribeB.setDisposable(b.subscribe(function (value) {
if (!updatingA) {
++updatingB;
a.onNext(bToA(value));
--updatingB;
}
});
return subscriptions;
};
var cmValue = new BehavoirSubject(0),
inValue = new BehaviorSubject(0),
binding = twowayBind(cmValue, inValue, cmToIn, inToCm);
Answer 4:
正如我的评论指出,这个问题并不需要一个循环。 它还不需要主题,或document.activeElement。 你可以不称呼对方流从输入事件更新B,和事件从输入B更新。
这里的例子:
http://jsfiddle.net/kcv15h6p/1/
相关位的位置:
var cmElement = document.getElementById('cm'),
inElement = document.getElementById('in'),
cmInputValue = Rx.Observable.fromEvent(cmElement, 'input').map(evToValue).startWith(0),
inInputValue = Rx.Observable.fromEvent(inElement, 'input').map(evToValue).startWith(0);
inInputValue.map(inToCm).subscribe(function (v) {
cmElement.value = myRound(v, 3).toFixed(3);
});
cmInputValue.map(cmToIn).subscribe(function (v) {
inElement.value = myRound(v, 3).toFixed(3);
});
对于那些真正需要循环的问题,您可以延迟创建循环,在布兰登的回答这个问题指出:
捉可观测量之间循环依赖
像任何循环结构,你必须处理的退出条件,以避免无限循环。 你可以像取(),或distinctUntilChanged运营商这样做()。 需要注意的是后者需要一个比较器,这样就可以,例如,使用对象标识(X,Y)=> X === Y,退出循环。
文章来源: Safe update for 2 dependent streams