【问题标题】:Crash with simple history push简单的历史推送崩溃
【发布时间】:2016-03-28 01:35:15
【问题描述】:

只是尝试一些愚蠢的东西并玩弄 Cycle.js。并遇到问题。基本上我只有一个按钮。当您单击它时,假设将位置导航到随机散列并显示它。几乎就像一个没有预定义路由的愚蠢路由器。 IE。路线是动态的。这又不是什么实用的东西,我只是在弄乱一些东西并尝试学习 Cycle.js。但是在我单击“添加”按钮后,下面的代码会崩溃。但是位置已更新。如果我实际上只是导航到“#/asdf”,它会使用“Hash:#/asdf”显示正确的内容。不知道为什么流程会因错误而崩溃:

render-dom.js:242 TypeError: Cannot read property 'subscribe' of undefined(...)

import Rx from 'rx';
import Cycle from '@cycle/core';
import { div, p, button, makeDOMDriver } from '@cycle/dom';
import { createHashHistory } from 'history';
import ranomdstring from 'randomstring';

const history = createHashHistory({ queryKey: false });

function CreateButton({ DOM }) {
  const create$ = DOM.select('.create-button').events('click')
    .map(() => {
      return ranomdstring.generate(10);
    }).startWith(null);

  const vtree$ = create$.map(rs => rs ?
    history.push(`/${rs}`) :
    button('.create-button .btn .btn-default', 'Add')
  );

  return { DOM: vtree$ };
}

function main(sources) {
  const hash = location.hash;
  const DOM = sources.DOM;

  const vtree$ = hash ?
    Rx.Observable.of(
      div([
        p(`Hash: ${hash}`)
      ])
    ) :
    CreateButton({ DOM }).DOM;

  return {
    DOM: vtree$
  };
}

Cycle.run(main, {
  DOM: makeDOMDriver('#main-container')
});

感谢您的帮助

【问题讨论】:

    标签: cyclejs


    【解决方案1】:

    我会进一步建议使用@cycle/history 来改变你的路线 (只显示相关部分)

    import {makeHistoryDriver} from '@cycle/history'
    import {createHashHistory} from 'history'
    
    function main(sources) {
      ...
      return {history: Rx.Observable.just('/some/route') } // a stream of urls
    }
    
    const history = createHashHistory({ queryKey: false })
    Cycle.run(main, {
      DOM: makeDOMDriver('#main-container'),
      history: makeHistoryDriver(history),
    })
    

    【讨论】:

    • 您好,感谢您的反馈。抱歉,我觉得自己很笨,但我无法使用 @cycle/history;我尝试了不同的方法并阅读了文档。如何将历史驱动程序与按钮的更改事件联系起来?我想结合这两个流吗?
    • 嗯,我想我终于弄明白了,谢谢你的帮助。
    【解决方案2】:

    在您的函数CreateButton 上,您将点击映射到history.push(),而不是将其映射到导致错误的vtree:

    function CreateButton({ DOM }) {
      ...
      const vtree$ = create$.map(rs => rs
        ? history.push(`/${rs}`) // <-- not a vtree
        : button('.create-button .btn .btn-default', 'Add')
      );
      ...
    }
    

    相反,您可以使用 do 运算符来执行 hashchange:

    function CreateButton({ DOM }) {
      const create$ = 
        ...
        .do(history.push(`/${rs}`)); // <-- here
    
      const vtree$ = Observable.of(
        button('.create-button .btn .btn-default', 'Add')
      );
      ...
    }
    

    但是在函数式编程中你不应该对你的应用逻辑执行副作用,每个函数都必须保持pure。相反,所有副作用都应由驱动程序处理。要了解更多信息,请查看drivers section on Cycle's documentation

    查看消息末尾的工作驱动程序跳转。


    此外,在您的 main 函数中,您没有使用流来呈现您的 vtree。它不会对 locationHash 更改做出反应,因为 vtree$ = hash ? ... : ... 仅在应用程序引导时评估一次(当评估主函数并将每个流“连接”在一起时)。

    一个改进是声明您的main 的vtree$ 如下,同时保持相同的逻辑:

    const vtree$ = hash$.map((hash) => hash ? ... : ...)
    

    这是一个完整的解决方案,带有一个小的 locationHash 驱动程序:

    import Rx from 'rx';
    import Cycle from '@cycle/core';
    import { div, p, button, makeDOMDriver } from '@cycle/dom';
    import { createHashHistory } from 'history';
    import randomstring from 'randomstring';
    
    
    function makeLocationHashDriver (params) {
      const history = createHashHistory(params);
    
      return (routeChange$) => {
        routeChange$
          .filter(hash => {
            const currentHash = location.hash.replace(/^#?\//g, '')
            return hash && hash !== currentHash
          })
          .subscribe(hash => history.push(`/${hash}`));
    
        return Rx.Observable.fromEvent(window, 'hashchange')
          .startWith({})
          .map(_ => location.hash);
      }
    }
    
    function CreateButton({ DOM }) {
      const create$ = DOM.select('.create-button').events('click')
        .map(() => randomstring.generate(10))
        .startWith(null);
    
      const vtree$ = Rx.Observable.of(
        button('.create-button .btn .btn-default', 'Add')
      );
    
      return { DOM: vtree$, routeChange$: create$ };
    }
    
    function main({ DOM, hash }) {
      const button = CreateButton({ DOM })
      const vtree$ = hash.map(hash => hash
        ? Rx.Observable.of(
            div([
              p(`Hash: ${hash}`)
            ])
          )
        : button.DOM
      )
    
      return {
        DOM: vtree$,
        hash: button.routeChange$
      };
    }
    
    Cycle.run(main, {
      DOM: makeDOMDriver('#main-container'),
      hash: makeLocationHashDriver({ queryKey: false })
    });
    

    PS:你的 randomstring 函数名有错字,我在我的例子中修正了它。

    【讨论】:

    • 嗯,有道理。应该想通了。感谢帮助!