Сгиб с начальным значением из localStorage в CycleJS
Я делаю приложение для просмотра смайликов с помощью cycleJS, где пользователь может щелкнуть любой смайлик, чтобы добавить / удалить его из списка избранных. Список также сохраняется в localstorage
на каждое изменение. Я строю список, используя xstream, сворачивая поток кликов (добавляя или удаляя эмодзи при каждом клике):
const favEmojis$ = clickFavEmoji$.fold(
(favList, selectedEmoji) =>
favList.includes(selectedEmoji)
? favList.filter(emoji => emoji !== selectedEmoji)
: [...favList, selectedEmoji],
[]
);
Я могу сохранить этот поток в localStorage
и загрузите его на страницу, используя драйвер @ cycle/ storage:
const storageRequest$ = favEmojis$.map(favEmojis => ({
key: "favEmojis",
value: JSON.stringify(favEmojis)
}));
...
return {
DOM: vdom$,
...
storage: storageRequest$
};
}
Однако я не могу понять, как предварительно загрузить массив из localStorage в любимый поток. Как только массив загружен из localStorage
Я попытался объединить / согласовать его с favEmojis$
поток во всех отношениях, о которых я мог думать. Например:
const storedEmojis$ = localStorage
.getItem("favEmojis")
.map(favEmojis => (favEmojis ? JSON.parse(favEmojis) : []))
.take(1);
const combinedFav$ = xs.merge(storedEmojis$, favEmojis$);
Но это не работает - массив из localstorage
перезаписывается при сворачивании clickFavEmoji
поток. Я был бы очень признателен, если бы кто-то указал мне правильное направление.
Примечание: полный код довольно длинный, поэтому я включил только те части, которые казались наиболее актуальными.
1 ответ
Проблема в том, что у вас есть два источника правды:
- значение, используемое в сгибе;
- значение в местном хранилище.
Оба источника вообще не зависят друг от друга, отсюда и странное поведение, которое вы испытываете.
Решением, которое могло бы работать, было бы создание редукторов от вас обоих clickFav$
а также storedEmojis$
, объединить и сложить их все вместе.
Вот как это будет выглядеть:
const clickReducer$ = clickFavEmoji$.map(
(favEmojis, selected) => /* same as you previous reducer */
);
const storedEmojisReducer$ = localStorage
.getItem("favEmojis")
.take(1)
.map(/* serialise to json */)
.map((state, favEmojis) => favEmojis) // here we just replace the whole state
const favEmojis$ = xs
.merge(storedEmojisReducer$, clickReducer$)
.fold(
(favEmojis, reducer) => reducer(favEmojis)
, [])
return {
DOM: favEmojis$.map(render)
}
Таким образом, существует явная связь между значением в localStorage и значением, которое развивается в течение жизненного цикла приложения.
С onionify
Теперь предыдущее решение работает хорошо. Когда редуктор вызывается, он знает о предыдущем значении, заданном localStorage. Но если вы посмотрите ближе на код, который создает favEmojis$
Это довольно много шума. У него нет конкретной бизнес-логики, он просто тупо называет данные редукторы.
onionify
( https://github.com/staltz/cycle-onionify) значительно упрощает процесс управления состоянием в циклическом приложении, централизуя все вызовы редукторов в одной точке и повторно внедряя новое состояние в источники вашего приложения.
Код не сильно изменится по сравнению с предыдущей версией, изменения будут: - состояние будет введено как явная зависимость вашего компонента; - вам не придется вручную вызывать редукторы.
function Component({ DOM, onion /* ... */ }) {
const clickReducer$ = /* same as before */
const storedEmojisReducer$ = /* same as before */
return {
DOM: onion
.state$ // the state is now inside onionify
.map(render),
// send the reducers to onionify
onion: xs.merge(storedEmojisReducer$, clickReducer$)
}
}