我正在尝试学习RxJS可观察量。在柜台上写了一个示例应用程序。
用户可以看到页面上所有计数器的总数。 (不是计数器的数量,而是所有计数器值的总和)。
例如:
页面上有两个分别为2,3的计数器。计数器总数应为2 + 3 = 5.
如果删除了计数器1,则计数器总计应为5-2 = 3
我遇到的问题是每当我点击删除按钮时,它都应从计数器总数中扣除该计数器值。我使用了一个主题来观察删除按钮,它已经解决了。
后来我添加了removeAll按钮,我面临着和以前类似的问题。我不能将计数器总流量设置为0.
我尝试了.last()方法,但由于observable没有结束,我无法获得计数器Total observable的最后状态。
我使用了merge()但它没有解决问题。
我无法解决它。
我想再次添加一个主题但是当我遇到这个问题两次时,现在我想知道是否有比添加主题更好的解决方案?或者我可能会遗漏一些东西。
```
// Code goes here
function createCounter(number){
return "<div class='counter' id='counter" + number + "'>" +
'<button id="increment' + number + '">+</button>' +
'<h1 style="display:inline-block; margin: 10px" id="counterValue' + number + '"></h1>' +
'<button id="decrement' + number + '">-</button>' +
'<button id="remove' + number + '">Remove</button>'
"</div>";
}
$(document).ready(function(){
var addCounter$ = Rx.Observable.fromEvent($("#addCounter"), 'click')
.map(()=> 1)
.startWith(0)
.scan((x,y) => x+y);
var countersSubject = new Rx.Subject();
var removeAll$ = Rx.Observable.fromEvent($("#removeAll"), 'click').map(() => 0).startWith(0);
var countersTotal$ = countersSubject.startWith(0).scan((x,y) => x+y).merge(removeAll$);
removeAll$.subscribe(() =>{
$('#counterContainer').empty();
});
countersTotal$.subscribe(total => {
$('#countersTotal').text(total);
});
addCounter$.subscribe(counterNum => {
$('#counterContainer').append(createCounter(counterNum));
var increment$ = Rx.Observable.fromEvent($("#increment" + counterNum), 'click')
.map(() => +1);
var decrement$ = Rx.Observable.fromEvent($("#decrement" + counterNum), 'click')
.map(() => -1);
var action$ = Rx.Observable.merge(increment$, decrement$);
var state$ = action$.startWith(0).scan((prev, now) => prev+now);
var counterSubs = state$.subscribe(val => {
$("#counterValue" + counterNum).text(val);
});
var countersTotalSubs = action$.subscribe(countersSubject);
var remove$ = Rx.Observable.fromEvent($("#remove" + counterNum), 'click');
var removeCounterTotal$ = Rx.Observable.combineLatest(remove$, state$,
(x,y) => -y);
removeCounterTotal$.subscribe(countersSubject);
remove$.subscribe(remove => {
countersTotalSubs.dispose();
counterSubs.dispose();
$('div').remove('#counter' + counterNum);
});
});
});
请在此处查看插件。 http://plnkr.co/flJx4LNRiboPmW0GjZtl
如果您需要什么,请告诉我。
由于
答案 0 :(得分:1)
你是对的,你可以在没有Subjects
的情况下做到这一点。我会说这是一个相当困难的挑战,立即采取行动(所以道具给你!),但仍然可行。
你已经有了一个非常好的开始,“一切都是一个流”,所以让我们打破这个。首先,每个计数器都是它自己的组件,它是几种不同流的合并(递增,递减,删除)。所以让我们从那开始,然后从那里向外移动。
首先要简化你的流增量,减少和删除,因为它也是一种行为:
$('#counterContainer').append(createCounter(counterNum));
//The map operator can take a value which it will map to every value it receives
var inc$ = Rx.Observable.fromEvent($('#increment' + counterNum), 'click').map(+1);
var dec$ = Rx.Observable.fromEvent($('#decrement' + counterNum), 'click').map(-1);
var remove$ = Rx.Observable.fromEvent($('#remove' + counterNum), 'click');
接下来,我们使用merge
+ scan
技术来保持计数器值的总计。
return Rx.Observable.merge(inc$, dec$)
.startWith(0)
.scan((prev, now) => prev + now);
但现在我们抛出第一个转折,我们知道我们只想取值直到要删除计数器(注意重点),进一步说,我们知道通过点击删除我们实际上想要从div中删除计数器。通过结合这些想法,我们可以添加两个新行为:
return Rx.Observable.merge(inc$, dec$)
.startWith(0)
.scan((prev, now) => prev + now)
//Complete when the remove button is clicked
.takeUntil(remove$)
//When completed remove this counter
.finally(() => $('div').remove('#counter' + counterNum))
//Show the value
.do(val => $('#counterValue' + counterNum).text(val));
为了让所有计数器能够同时运行,我们应该将它们合并在一起,因为我们实际上需要两个值,一个总值和一个delta值,我们将拆分两个流,一个将在内部用于更新计数器的值和另一个将是将用于更新总价值的增量。
为了做到这一点,您可以使用share
和shareReplay
以及using
运算符将所有这些流绑定在一起。
//flatMap has an index parameter which can be used here to tally the total
//number of counters "in-flight"
var counters = addCounter$.flatMap((counterNum, idx) => {
$('#counterContainer').append(createCounter(counterNum));
var inc$ = Rx.Observable.fromEvent($('#increment' + counterNum), 'click').map(+1);
var dec$ = Rx.Observable.fromEvent($('#decrement' + counterNum), 'click').map(-1);
//Merges all the events together to describe their logic and then
//shares the resulting Observable
var counter = Rx.Observable.merge(inc$, dec$)
.takeUntil(remove$)
.startWith(0)
.share();
//Creates an Observable that will always emit the last value it
//recieved to all new subscribers
var total = counter
.scan((prev, now) => prev + now, 0)
.shareReplay(1);
return Rx.Observable.using(
//Starts the `total` Observable and updates the counter value
//when a button is pressed
//Ties the subscription's lifetime to that of `counter`
() => total.subscribe(val => $('#counterValue' + counterNum).text(val)),
//Returns the counter Observable
() => counter
)
.finally(() => $('div').remove('#counter' + counterNum))
//When the above Observable completes we will emit one last message
//which will be the total * -1 (subtracting the value from the overall total)
.concat(total.last().map(x => x * -1));
});
现在定义了addCounter$
:
var addCounter$ = Rx.Observable.fromEvent($("#addCounter"), 'click')
//Map also takes an index parameter which can be leveraged here
.map((_, idx) => idx);
最后,我们需要将所有这些组合在一起,最后一个难题是removeAll
能力。到目前为止我们所看到的所有内容都可以被认为是这个功能的子流,因为删除all有点像“恢复”状态。我们可以使用counters
流并将其包装在每次单击remove all
时重新启动的流中,并且因为我们的内部Observables会自动清理,所以我们也会在此过程中神奇地删除它们。 / p>
var removeAll$ = Rx.Observable.fromEvent($("#removeAll"), 'click');
removeAll$
.startWith(0)
.flatMapLatest(() => {
resetTotal();
//Total all the deltas from all of the counters
return adder.scan((acc, val) => acc + val);
})
.subscribe(x => $('#countersTotal').text(x));
这就是它的全部!请参阅上面的更新的plunkr以及工作示例(也在下面复制)。
// Code goes here
function createCounter(number){
return "<div class='counter' id='counter" + number + "'>" +
'<button id="increment' + number + '">+</button>' +
'<h1 style="display:inline-block; margin: 10px" id="counterValue' + number + '"></h1>' +
'<button id="decrement' + number + '">-</button>' +
'<button id="remove' + number + '">Remove</button>'
"</div>";
}
$(document).ready(function(){
var addCounter$ = Rx.Observable.fromEvent($("#addCounter"), 'click')
.map((_, idx) => idx);
function resetTotal() {
$('#countersTotal').text(0);
}
var removeAll$ = Rx.Observable.fromEvent($("#removeAll"), 'click');
var adder = addCounter$.flatMap((counterNum, idx) => {
$('#counterContainer').append(createCounter(counterNum));
var inc$ = Rx.Observable.fromEvent($('#increment' + counterNum), 'click').map(+1);
var dec$ = Rx.Observable.fromEvent($('#decrement' + counterNum), 'click').map(-1);
var remove$ = Rx.Observable.fromEvent($('#remove' + counterNum), 'click');
var counter = Rx.Observable.merge(inc$, dec$)
.takeUntil(remove$)
.startWith(0)
.share();
var total = counter
.scan((prev, now) => prev + now, 0)
.shareReplay(1);
var d = total
.subscribe(val => $('#counterValue' + counterNum).text(val));
return Rx.Observable.using(() => d, () => counter)
.finally(() => $('div').remove('#counter' + counterNum))
.concat(total.last().map(x => x * -1));
});
removeAll$
.startWith(0)
.flatMapLatest(() => {
resetTotal();
return adder.scan((acc, val) => acc + val);
})
.subscribe(x => $('#countersTotal').text(x));
});
<!DOCTYPE html>
<html>
<head>
<script data-require="jquery@2.2.0" data-semver="2.2.0" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script data-require="rxjs@4.1.0" data-semver="4.1.0" src="//cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>
<script src="script.js"></script>
</head>
<body>
<div id ="app">
<button id="addCounter">Add counter</button>
<button id="removeAll">Remove all</button>
<h1 >Counters Total <span id="countersTotal" ></span></h1>
<div id="counterContainer"></div>
</div>
</body>
</html>