Цикл Js - Подход к синхронизации истории с государством?
Я пытаюсь синхронизировать историю браузера с состоянием, содержащимся в хранилище состояний onionify в очень простом цикле js пример приложения. У меня нет проблем с отображением из хранилища состояний в историю, проблема заключается в сокращении history.state обратно в хранилище состояний. По сути, я попал в бесконечный цикл потоков состояний, перетекающих друг в друга, это приводит к сбою браузера.
import { run } from "@cycle/run";
import { div, button, p, makeDOMDriver } from "@cycle/dom";
import onionify from "cycle-onionify";
import { makeHistoryDriver } from "@cycle/history";
import xs from "xstream";
const main = (sources) => {
const popHistory$ = sources.history; // This is the problem...
const action$ = xs.merge(
sources.DOM.select(".decrement").events("click").map( () => -1 ),
sources.DOM.select(".increment").events("click").map( () => +1 ),
);
const state$ = sources.onion.state$;
const vdom$ = state$.map( state =>
div([
button(".decrement", "Decrement"),
button(".increment", "Increment"),
p(`Count: ${state.count}`)
])
);
const initReducer$ = xs.of( function initReducer() {
return {count: 0};
});
const updateReducer$ = action$.map( x => function reducer(prevState) {
return {count: prevState.count + x};
});
// this is where the inifinity loop starts
const historyReducer$ = popHistory$.map( history => function(prevState) {
return {count: history.state.count};
});
// You can't merge historyReducer$ here
const reducer$ = xs.merge(initReducer$, updateReducer$, historyReducer$);
const pushHistory$ = state$.map( state => ({
type: "push",
pathname: `/?count=${state.count}`,
state: state
}));
return {
DOM: vdom$,
onion: reducer$,
history: pushHistory$,
debug: popHistory$
}
};
const onionMain = onionify(main);
run(onionMain, {
DOM: makeDOMDriver("#main"),
history: makeHistoryDriver(),
debug: $ => $.subscribe({next: console.log})
});
Я думаю, мой вопрос: есть ли более простой способ сделать это? Есть ли оператор, который помогает здесь? Я чувствую, что то, что я пытаюсь сделать, в принципе невозможно. Будем благодарны за любые ответы или ссылки на полезные ресурсы.
2 ответа
Учитывая, что я понял это, я подумал, что я могу также опубликовать минимальный рабочий пример маршрутизации с историей / состоянием в хранилище состояний лука. Это может быть полезно для кого-то в будущем в качестве ссылки. Ответ состоял в том, чтобы отфильтровать данные, возвращающиеся в историю из исторических публикаций.
import { run } from "@cycle/run";
import { div, button, p, makeDOMDriver } from "@cycle/dom";
import { makeHistoryDriver } from "@cycle/history";
import onionify from "cycle-onionify";
import xs from "xstream";
const main = (sources) => {
const {
DOM: domSource$,
history: historySource$,
onion: onionSource
} = sources;
const action$ = xs.merge(
domSource$.select(".decrement").events("click")
.mapTo( state => ({...state, count: state.count - 1, avoidHist: false}) ),
domSource$.select(".increment").events("click")
.mapTo( state => ({...state, count: state.count + 1, avoidHist: false}) ),
historySource$.filter( e => e.state !== undefined ) // essentially captures forward and back button events
.map( e => e.state.count )
.map( count => state => ({...state, count, avoidHist: true}) ),
historySource$.filter( e => e.state === undefined && e.hash !== "" ) // capture hash change events and grab state data with regex
.map( e => e.hash )
.map( hashStr => hashStr.match(/count=(-?\d{1,16})/) )
.filter( val => val !== null )
.map( arr => arr[1] )
.map( str => Number(str) )
.map( count => state => ({...state, count, avoidHist: true}) ) // add state property for filtering history
);
const state$ = onionSource.state$;
const initReducer$ = xs.of( () => ({count: 0}) );
const onionSink$ = xs.merge(initReducer$, action$);
const domSink$ = state$.map( state =>
div([
button(".decrement", "Decrement"),
button(".increment", "Increment"),
p(`Count: ${state.count}`)
])
);
const historySink$ = state$.filter( state => state.avoidHist !== true ) // filter for avoid history property
.map( state => ({
type: "push",
pathname: `#count=${state.count}`,
state
}));
return {
DOM: domSink$,
history: historySink$,
onion: onionSink$
};
};
const onionMain = onionify(main);
run(onionMain, {
DOM: makeDOMDriver("#main"),
history: makeHistoryDriver()
});
Я предполагаю, что ключом к созданию поведения приложения, которое имитирует взаимодействие серверных веб-страниц с историей, является то, что ни одно из отображений из истории popstate не возвращается в историю. Кажется очевидным сейчас, но мне потребовалось некоторое время, чтобы понять это.
Из того, что я могу понять, я полагаю, что вам просто не хватает одного dropRepeats
на потоке, который питает history
Водитель.
Это не кусок кода, который я протестировал, но я считаю, что заменить эту строку
const pushHistory$ = state$.map(...)
с
import dropRepeats from 'xstream/extra/dropRepeats'
// ....
const pushHistory$ = state$.compose(dropRepeats()).map(...)
Исправлю твою проблему.
Это отфильтрует pushState, когда состояние на самом деле не изменилось, но будет синхронизировать URL-адрес с тем, что находится в этом состоянии, и вам не потребуется менять все прослушиватели событий, как вы делали в предыдущем ответе:)
В дополнениях к xstream есть несколько интересных операторов https://github.com/staltz/xstream/blob/master/EXTRA_DOCS.md