Идиоматический способ изменить свойство с несколькими событиями, используя кефир

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

В моем проекте я начал использовать 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);

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

По многим причинам я решил попробовать библиотеку, отличную от RxJ, и сейчас оцениваю 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));

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

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);
}

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

Другие вопросы по тегам