Идиоматический способ изменить свойство с несколькими событиями, используя кефир
Какой идиоматический способ создать свойство в кефире, которое изменяется в ответ на несколько типов событий?
В моем проекте я начал использовать 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);
}
По сути, он связывает каждое событие с функцией мутации, объединяет эти пары, а затем просматривает их, применяя функцию мутации к исходному значению и событию. Я не уверен, что это действительно лучше, но это кажется более "функциональным".