【问题标题】:Cycle Js - Approach to Synchronizing History with State?Cycle Js - 将历史与状态同步的方法?
【发布时间】:2017-12-11 04:48:05
【问题描述】:

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

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

【问题讨论】:

  • 根据代码,historyReducer$改变洋葱状态,洋葱状态(通过pushHistory$)改变历史状态。这与流或 Cycle.js 无关,它只是在一个循环中相互改变的两件事。所以我需要进一步澄清:你想要实现什么功能?
  • 你说得对。这只是一个无限循环的问题。我对如何将历史源连接到状态以及状态返回到历史接收器感到困惑。
  • 我通过不将历史源连接到历史接收器解决了这个问题,并且一切都按预期工作。我的目标是制作一个非常简单的路由示例,因为我找不到。我会发布我的答案,它可能对第一次学习循环客户端路由的人有用。

标签: javascript reactive-programming cyclejs


【解决方案1】:

考虑到我想通了这一点,我想我不妨发布一个使用历史/状态路由与洋葱状态存储的最小工作示例。它可能对将来的某人有用作为参考。答案是从历史弹出状态中过滤掉流回历史的数据。

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 的映射不会流回历史。现在看起来很明显,但我花了一段时间才弄清楚。

【讨论】:

    【解决方案2】:

    据我所知,我相信您只是在为history 驱动程序提供数据的流上缺少一个dropRepeats

    这不是我测试过的一段代码,但我相信替换这一行

    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 的附加功能中有一些有趣的运算符

    【讨论】:

      猜你喜欢
      • 2013-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-27
      • 2013-09-04
      • 1970-01-01
      相关资源
      最近更新 更多