0

我正在尝试在一个非常简单的循环 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})
});

我想我的问题是:有没有更简单的方法可以做到这一点?这里有帮助的操作员吗?我觉得我想做的事情基本上是不可能的。任何有用资源的答案或链接将不胜感激。

4

2 回答 2

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

于 2017-12-14T16:30:17.180 回答
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()
});

我猜想创建模拟服务器呈现的网页与历史交互方式的应用程序行为的关键在于,历史 popstate 的映射不会流回历史。现在看起来很明显,但我花了一段时间才弄清楚。

于 2017-12-13T02:48:38.250 回答