困在使用RxJS删除所有计数器应用程序中



我正在尝试学习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

如果你需要什么,请告诉我。

感谢

你是对的,你可以在没有Subjects的情况下完成。我要说的是,这是一个相当困难的挑战,很难马上上手(所以这是你的道具!),但尽管如此,这肯定是可行的。

;tldr请在此处查看更新的plunkr

解释

你在"一切都是一股洪流"方面有一个很好的开端,所以让我们来分解一下。首先,每个计数器都有自己的组成部分,它是几个不同流(递增、递减、移除)的融合。因此,让我们从这个开始,并从那里向外移动。

首先,简化您的流以进行增量、减量删除,因为这也是一种行为:

$('#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值,我们将拆分出两个流,一个将在内部用于更新计数器的值,另一个将是delta,用于更新总值。

为了做到这一点,您可以使用shareshareReplay以及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功能。到目前为止,我们看到的所有东西都可以被视为该功能的子流,因为remove-All有点像"恢复"状态。我们可以获取counters流,并将其封装在每次单击remove all时重新启动的流中,因为我们的内部Observables会自动清理,所以我们也会在这个过程中神奇地将它们全部删除。

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>

相关内容

最新更新