Идиоматический способ изменить свойство с несколькими событиями с помощью Kefir

Каков идиоматический способ создания свойства в Kefir, которое изменяется в ответ на несколько типов событий?

В моем проекте я начал использовать rxjs для приложения в стиле FRP. В этом приложении я хотел подписаться на состояние, которое изменилось в ответ на несколько переменных. Вот как я вроде заработал:

const subject = new BehaviorSubject([]);

addEvents
  .withLatestFrom(subject, (newItem, items) => items.concat(newItem))
  .subscribe(subject);

removeEvents
  .withLatestFrom(subject, (item, items) => {
    return items.filter(i => i !== item);
  })
  .subscribe(subject);

Я мог бы сказать, что это, вероятно, не лучшая практика; это не кажется идиоматичным, и я также только что понял, что подписка наблюдателя на несколько источников не совсем корректна.

По многим причинам я решил попробовать библиотеку, отличную от RxJs, и сейчас оцениваю Kefir, у которого отличная документация и предположительно более высокая производительность. Но мне еще труднее определить, как делать то, что я хотел бы, если не считать уродливых хаков, где мне придется проверять типы событий:

kefir
  .merge(addEvents, removeEvents)
  .scan(
    (items, event) => {
      switch (event.type) {
        case 'add': return items.concat(event.item);
        case 'remove': return items.filter(i => i !== event.item);
      }
    },
    [])
  .toProperty();

Я действительно предпочел бы, чтобы не приходилось использовать неэлегантные методы, такие как большие условные блоки для множества типов событий, чтобы создать поток изменений.

Я не планирую использовать Bacon.js, но вижу, что в нем есть именно то, что мне нужно:

Bacon.update([],
  [addEvents],    (items, evt) => items.concat(evt.item),
  [removeEvents], (items, evt) => items.filter(i => i !== evt.item));

Есть ли естественный способ делать такие вещи с Kefir с его стандартными операторами, или это то, что мне в конечном итоге придется реализовать самому?


person Jacob    schedule 02.08.2015    source источник


Ответы (1)


Я понял этот подход, который мне не в восторге, но, по крайней мере, код довольно чистый:

const items = function () {
  let items = [];  
  return kefir
    .merge([
      addEvents.map(item => items = items.concat(item)),
      removeEvents.map(item => items = items.filter(i => i !== item))
    ])
    .toProperty(() => items);
}();

Что мне не нравится в этом, так это то, что он изменяет состояние, но, поскольку JavaScript является однопоточным, и я скрываю это состояние, возможно, это не так уж плохо.

Вот это как функция полезности:

import kefir from 'kefir';

export default function dynamicValue(initValue, ...args) {
  let value = initValue;
  let streams = [];
  while (args.length) {
    let [source, xform, ...remaining] = args;
    streams.push(source.map(v => value = xform(value, v)));
    args = remaining;
  }

  return kefir.merge(streams).toProperty(() => value);
}

...используется так:

dynamicValue([],
  addEvents, (items, item) => items.concat(item),
  removeEvents, (items, item) => items.filter(i => i !== item));

Обновление:

Нашел другой способ реализации dynamicValue с использованием map, merge и scan без необходимости изменять переменную:

import kefir from 'kefir';

export default function transform(initValue, ...args) {
  let mutations = [];
  while (args.length) {
    let [source, calculateNewValue, ...newArgs] = args;
    mutations.push(source.map(e => ({ event: e, mutation: calculateNewValue })));
    args = newArgs;
  }

  return kefir
    .merge(mutations)
    .scan((prev, { event, mutation }) => mutation(prev, event), initValue);
}

По сути, он связывает каждое событие с функцией мутации, объединяет эти пары, затем сканирует их, применяя функцию мутации к исходному значению и событию. Я не уверен, действительно ли он лучше, но он кажется более "функциональным".

person Jacob    schedule 02.08.2015
comment
мне кажется путь. Вместо переменной items в области видимости я бы использовал scan или аналогичный метод. - person Eryk Napierała; 04.08.2015