懒惰地在react-router onEnter挂钩中添加新史诗是一种有效的做法吗?

cla*_*ani 3 reactjs redux redux-observable redux-router

当使用终极版,可观察到有反应的路由器,这将是有意义的异步的文档中添加新的史诗按照指示在这里发生反应,路由器的OnEnter挂钩期间内路由变化?

./epics/index.js

import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { combineEpics } from 'redux-observable';

import { epic1 } from './epic1'
import { epic2 } from './epic2'

export const epic$ = new BehaviorSubject(combineEpics(epic1, epic2));
export const rootEpic = (action$, store) =>
    epic$.mergeMap(epic =>
        epic(action$, store)
    );

export {
    rootEpic
};
Run Code Online (Sandbox Code Playgroud)

...路线/ SomePath/index.js

import { epic$ } from './../../../../epics/index'
import { epic3 } from './../../../../epics/epic3'

module.exports = {
    path: 'some-path',
    getComponent( location, cb ) {
        require.ensure( [], ( require ) => {
            cb( null, require( './components/SomePath' ) )
        } )
    },
    onEnter() {
        epic$.next(epic3)

    }
}
Run Code Online (Sandbox Code Playgroud)

Rxjs和redux-observable非常新.这似乎有效,但想知道:1.在这种情况下,每当我们导航到/ some-path时,epic3会再次添加到rootEpic吗?2.如果我想在console.log中将哪些史诗添加到rootEpic中,我该怎么做?


编辑以回应@JayPhelps

我可以要求澄清几点吗?

  1. registerEpic fn效果很好.我一直在使用.distinct()运算符来解决重复的史诗注册问题,如下所示:

    export const epic$ = new BehaviorSubject(combineEpics(epic1, epic2)).distinct()

这是处理懒惰史诗注册的同等有效/好方法,如果没有,你能解释一下原因吗?

  1. 我正在使用create-react-app,它有require.ensure和ES6导入(它们是不同的导入方式吗?),基本上,我复制粘贴react-router的巨大应用程序示例,其中包含require.ensure代码,但无处不在否则,我在文件顶部使用import语句.

因此,在顶部导入epics和registerEpic fn似乎是有效的,而将require.ensure第一个参数中的路径放入也似乎有效.如果我使用require.ensure和import语句,它会变得混乱吗?如果我使用require.ensure加载路由需要的一切,这是否意味着我删除该路由组件的嵌套(无路由)组件内的所有import语句?

import { epic3 } from './../../epics/epic3'
import { epic4 } from './../../epics/epic4'
import { registerEpic } from './../../epics/index'

module.exports = {
    path: 'my-path',
    getComponent( location, done ) {
        require.ensure( [], ( require ) => {
           registerEpic(addYoutubeEpic)
            registerEpic(linkYourSiteEpic)
            done( null, require( './components/Edit' ) )
        } )
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 关于在'getComponent'fn中为异步加载注册史诗 - 我认为实际上需要在此处进行同步加载,以便在注册史诗之前不会呈现路径的组件.如果用户试图在路线上做某事,比如获取一些详细信息或提交表格,需要登记史诗,史诗还没有注册,会发生什么?

  2. 是否有任何其他地方的react-router module.export声明适合懒惰注册史诗?由于我的项目中的许多路由不需要登录,因此登录并不总是触发路由更改.然而,在用户登录后应该注册一堆史诗.目前,我通过在我的authReducer中设置一个令牌变量以一种非常愚蠢和可能错误的方式来做它,但它只是调用一个函数注册新史诗:

    './../reducers/authReducer.js'
    
    import { greatEpic } from './../epics/fetchFollowListEpic'
    import { usefulEpic } from './../epics/fetchMyCollectionsEpic'
    // and a few more 
    import { registerEpic } from './../epics/index'
    
    const addEpics = () => {
        registerEpic(greatEpic)
         registerEpic(usefulEpic)
         ...etc
    }
    
    export default function reducer( state = {
        loggingIn: false,   
        loggedIn: false,
        error: '',
    }, action ) {
        switch ( action.type ) {
            case "LOGIN_SEQUENCE_DONE": {
                return {
                    ...state,
                    aid: setAuthToken(action.payload),
                    loginFailed: false,
                    addEpics: addEpics(),
                }
    
            }
    
    Run Code Online (Sandbox Code Playgroud)

我会想象loginEpic会是一个更好的注册史诗的地方,而不是reducer.(我在github上找到了你的登录示例,所以看起来很熟悉).这是正确的,还是我完全偏离基地?我知道.concat()是同步的,我知道redux是同步的 - 我不确定registerEpics()是否是同步的,但是使用reducer注册史诗SEEMS才能正常工作 - 这是否意味着registerEpics是同步的?或者它只是意味着事情发射如此之快以至于无关紧要?或者它是否只能起作用,因为我对import语句有一些重要的误解,以及webpack如何处理代码拆分,我还要研究更多?

./../epics/loginEpic

export const loginEpic = action$ =>
    action$.ofType("LOGIN")
        .mergeMap(action =>

            Observable.fromPromise(axios.post('webapi/login', action.payload))
                .flatMap(payload =>
                    // Concat multiple observables so they fire sequentially
                    Observable.concat(
                        // after LOGIN_FULILLED
                        Observable.of({ type: "LOGIN_FULFILLED", payload: payload.data.aid }),                   
                        // ****This is where I think I should be registering the epics right after logging in. "ANOTHER_ACTION" below depends upon one of these epics****     
                        Observable.of({type: "ANOTHER_ACTION", payload: 'webapi/following'}),
                        Observable.of({type: "LOGIN_SEQUENCE_DONE"}),
                    )
                )
                .startWith({ type: "LOGIN_PENDING" })
                .takeUntil(action$.ofType("LOGIN_CANCELLED"))
                .catch(error => Observable.of({
                    type: "LOGIN_REJECTED",
                    payload: error,
                    error: true
                }))
        );
Run Code Online (Sandbox Code Playgroud)

jay*_*lps 6

在这种情况下,每当我们导航到/ some-path时,epic3会再次添加到rootEpic吗?

如果您使用的反应路由器和做代码分裂,你居然要添加您的内部必要的史诗getComponent钩,没有onEnter钩.这是因为getComponent钩子允许异步加载该路由所需的资源,不仅仅是组件,还包括任何史诗,require.ensure()缩减器等.如果你使用,这告诉webpack将这些项目拆分成一个单独的包,这样它们就不包含在原始条目 - 所以不要在其他地方导入这些文件,否则你会否定这个!

所以大致这是一个可能的例子:

路由/ SomePath/index.js

import { registerEpic } from 'where/ever/this/is/epics/index.js';

export default {
  path: 'some-path',

  getComponent(location, done) {
    require.ensure(['./components/SomePath', 'path/to/epics/epic3'], require => {
      // this is where you register epics, reducers, etc
      // that are part of this separate bundle
      const epic = require('path/to/epics/epic3').epic3;
      registerEpic(epic);

      done(null, require('./components/SomePath'));
    });
  }
};
Run Code Online (Sandbox Code Playgroud)

这里的/永远/这/是/史诗/ index.js

import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { combineEpics } from 'redux-observable';

import { epic1 } from './epic1';
import { epic2 } from './epic2';

const epicRegistry = [epic1, epic2];
const epic$ = new BehaviorSubject(combineEpics(...epicRegistry));

export const registerEpic = (epic) => {
  // don't add an epic that is already registered/running
  if (epicRegistry.indexOf(epic) === -1) {
    epicRegistry.push(epic);
    epic$.next(epic);
  }
};

// your root epic needs to use `mergeMap` on the epic$ so any
// new epics are composed with the others
export const rootEpic = (action$, store) =>
  epic$.mergeMap(epic =>
    epic(action$, store)
  );
Run Code Online (Sandbox Code Playgroud)

我创建了一个注册函数,确保你没有添加已添加的史诗; getComponent 每次进入该路由时,react-router都会调用,因此这很重要,因此您没有同一个史诗的两个正在运行的实例.

但请注意,我的代码会做出一些假设,这些假设在开箱即用时可能是错误的require('path/to/epics/epic3').epic3.你的epic3实际上是作为命名属性导出的吗?我猜是这样,但我注意到你正在做这个done(null, require('./components/SomePath')),如果你使用export defaultES2015/ES6模块导出该组件,我的直觉告诉我可能无法正常工作.如果这是真的,它实际上发现在 - require('./components/SomePath').default注意.default!因此,虽然我的代码是一般的想法,你应该特别注意需求路径,导出的属性等.你说它似乎工作,所以也许你的设置是不同的(或者我只是错了呵呵)


  1. 如果我想在console.log中将哪些史诗添加到rootEpic中,我该怎么做?

最简单的方法是登录registerEpic实用程序:

export const registerEpic = (epic) => {
  // don't add an epic that is already registered/running
  if (epicRegistry.indexOf(epic) === -1) {
    epicRegistry.push(epic);
    console.log(`new epic added: ${epic.name}`);
    epic$.next(epic);
  }
};
Run Code Online (Sandbox Code Playgroud)

但是你也可以.do()在你的epic$主题上使用RxJS 运算符更具体地做到这一点:

export const rootEpic = (action$, store) =>
  epic$
    .do(epic => console.log(`new epic added: ${epic.name || '<anonymous>'}`))
    .mergeMap(epic =>
      epic(action$, store)
    );
Run Code Online (Sandbox Code Playgroud)

然而,这将<anonymous>是第一次记录,因为第一次出现的结果是combineEpics(...epicRegistry)将多个史诗组合成一个匿名史诗.