周期Js - 历史与国家同步的方法?

时间:2017-12-11 04:48:05

标签: javascript reactive-programming cyclejs

我正在尝试在一个非常简单的循环js示例应用程序中将浏览器历史记录与onionify状态存储中包含的状态同步。我从状态存储映射到历史没有问题,问题在于减少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 个答案:

答案 0 :(得分:0)

考虑到我想出了这个,我想我也可以发布一个带有洋葱状态商店的历史/状态路由的最小工作示例。它可能对将来作为参考有用。答案是过滤掉从历史流行者流回历史的数据。

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

我认为创建应用程序行为的关键在于模仿服务器呈现的网页与历史记录交互的方式,因为历史记录popmap中的映射不会流回历史记录。现在似乎显而易见,但我花了一段时间来推理它。

答案 1 :(得分:0)

根据我的理解,我相信您只是在流中为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

的附加内容中有一些有趣的运算符