最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - RxJS - make counter with reset stateless? - Stack Overflow

programmeradmin2浏览0评论

Assuming I have the following markup:

<button id="dec">-</button>
<output id="out">0</output>
<button id="inc">+</button>
<button id="res">RESET</button>

And the following Rx.js script:

var total = 0

Rx.Observable.merge(
    // decrement
    Rx.Observable.fromEvent($('#dec'), 'click')
    .map(function() { return -1 }),

    // increment
    Rx.Observable.fromEvent($('#inc'), 'click')
    .map(function() { return +1 }),

    // reset
    Rx.Observable.fromEvent($('#res'), 'click')
    .map(function() { return -total })
) // merge
.forEach(function(delta) {
    total += delta
    $('#out').text(total)
})

Everything works as expected. Clicking the +/- increments/decrements the counter, and clicking 'RESET' resets it back to zero, but...I have the variable 'total' at the top. This is state, which if I subscribe to the values of functional reactive programming, is evil, no? If so, how do I remedy this? If I didn't have the reset button, I could just use scan(seed, accumulator), but the functionality of the reset button is throwing me for a loop, as to how to do it 'stateless'.

Working fiddle here.

Assuming I have the following markup:

<button id="dec">-</button>
<output id="out">0</output>
<button id="inc">+</button>
<button id="res">RESET</button>

And the following Rx.js script:

var total = 0

Rx.Observable.merge(
    // decrement
    Rx.Observable.fromEvent($('#dec'), 'click')
    .map(function() { return -1 }),

    // increment
    Rx.Observable.fromEvent($('#inc'), 'click')
    .map(function() { return +1 }),

    // reset
    Rx.Observable.fromEvent($('#res'), 'click')
    .map(function() { return -total })
) // merge
.forEach(function(delta) {
    total += delta
    $('#out').text(total)
})

Everything works as expected. Clicking the +/- increments/decrements the counter, and clicking 'RESET' resets it back to zero, but...I have the variable 'total' at the top. This is state, which if I subscribe to the values of functional reactive programming, is evil, no? If so, how do I remedy this? If I didn't have the reset button, I could just use scan(seed, accumulator), but the functionality of the reset button is throwing me for a loop, as to how to do it 'stateless'.

Working fiddle here.

Share Improve this question asked Jul 15, 2015 at 15:25 Jonathan ApodacaJonathan Apodaca 5,7886 gold badges33 silver badges41 bronze badges
Add a comment  | 

3 Answers 3

Reset to default 20

There are two ways of going about this that I can see.

First, there is nothing that says you can't augment your data in the pipeline:

Rx.Observable.merge(
  // decrement
  Rx.Observable.fromEvent($('#dec'), 'click')
    .map(function() { return {delta : -1}; }),

  // increment
  Rx.Observable.fromEvent($('#inc'), 'click')
    .map(function() { return {delta : +1}; }),

  // reset
  Rx.Observable.fromEvent($('#res'), 'click')
    .map(function() { return {reset : true}; })
) // merge
.scan(0, function(acc, value) {
  return value.reset ? 0 : acc + value.delta;
})
.forEach(function(delta) {
  $('#out').text(delta)
});

The above lets you signal downstream that the stream has been reset by adding in a field (note: I cheated, for readability you might want to add reset : false rather than rely on falsey-ness, but it's up to you).

Alternatively, if you think of the reset as actually resetting the stream then you could instead use flatMapLatest to wrap the incrementing and decrementing:

Rx.Observable.fromEvent($('#res'), 'click')
.startWith(null)
.flatMapLatest(function(e) {
  return Rx.Observable.merge(
    // decrement
    Rx.Observable.fromEvent($('#dec'), 'click')
      .map(function() { return -1 }),

    // increment
    Rx.Observable.fromEvent($('#inc'), 'click')
      .map(function() { return +1 })
  )
  .scan(0, function(acc, delta) { return acc + delta })
  .startWith(0);
})
.subscribe(function(value) {
   $('#out').text(value) 
});

This makes the stream a little bit messier than it has to be with the inclusion of two .startWiths to kick off the respective sequences, but if you were opposed to augmenting and wanted the state implicitly controlled by the stream then this would be an approach.

Following @paulpdaniels answer, here is what i'm using with Ramda:

var hardSet  = Rx.Observable.fromEvent($('#set');
var decRes   = Rx.Observable.fromEvent($('#dec');
var incRes   = Rx.Observable.fromEvent($('#inc');

Rx.Observable.merge(
  incRes.map(function()  { return R.add(1); }),
  decRes.map(function()  { return R.add(-1); }),
  hardSet.map(function() { return R.always(0); })
).scan(function(prev, f) { 
  return f(prev); 
}, 0);

Here is how it (reset + input for changing step) could be accomplished RxJs v6:

const { fromEvent, merge } = rxjs;
const { map, mapTo, startWith, scan, withLatestFrom } = rxjs.operators;

const resultEl = document.getElementById("js-result"),
  stepInpEl = document.getElementById("js-step-inp"),
  btnDecEl = document.getElementById("js-btn-dec"),
  btnIncEl = document.getElementById("js-btn-inc"),
  btnReset = document.getElementById("js-btn-reset");

// observables (5)
const step$ = fromEvent(stepInpEl, "input").pipe(
  startWith({ target: { value: 5 } }),
  map(e => Number(e.target.value))
);

const inc$ = fromEvent(btnIncEl, "click").pipe(
  withLatestFrom(step$),
  map(([e, step]) => step )
);

const dec$ = fromEvent(btnDecEl, "click").pipe(
  withLatestFrom(step$),
  map(([e, step]) => -step )
);

const reset$ = fromEvent(btnReset, "click").pipe(
  mapTo( 0 )
);

const counter$ = merge(dec$, inc$, reset$).pipe(
  startWith( 0 ),
  scan((acc, value) => value && acc + value)
);

// subscriptions (2)
counter$.subscribe(val => resultEl.innerHTML = val);
step$.subscribe(val => stepInpEl.value = val);

markup:

<h2>RxJS (v6) counter</h2>
<em>5 observables, 2 subscriptions</em>
<h3>Result: <span id="js-result"></span></h3>

<button id="js-btn-reset">Reset</button>

<input type="number" id="js-step-inp" class="stepInp">

<button id="js-btn-dec">Decrement</button>

<button id="js-btn-inc">Increment</button>

Live example on codepen

发布评论

评论列表(0)

  1. 暂无评论