4

将 redux-observable 与 react-router 一起使用时,在路由更改期间按照文档中的说明在 react-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
};

...路线/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)

    }
}

Rxjs 和 redux-observable 非常新。这似乎可行,但想知道: 1. 在这种情况下,每次我们导航到 /some-path 时,epic3 会再次添加到 rootEpic 吗?2. 如果我想控制台记录哪些史诗已添加到 rootEpic 中,我该怎么做?


编辑以回应@JayPhelps

我可以要求澄清几点吗?

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

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

这是处理惰性史诗注册的同样有效/好的方法吗?如果不是,您能解释一下原因吗?

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

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

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' ) )
        } )
    }
}
  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(),
                }
    
            }
    

我想 loginEpic 将是注册史诗的更好地方,而不是 reducer。(我在 github 上找到了您的登录示例,因此它可能看起来很熟悉)。这是正确的,还是我完全偏离了基地?我知道 .concat() 是同步的,而且我知道 redux 是同步的——我不确定 registerEpics() 是否同步,但是使用 reducer 注册史诗似乎可以正常工作——这是否意味着 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
                }))
        );
4

1 回答 1

6

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

如果您正在使用 react-router 并进行代码拆分,您实际上会想要在您的钩子中添加必要的史诗,getComponent不是onEnter钩子。这是因为getComponent钩子允许异步加载该路由所需的资源,不仅是组件,还包括任何史诗、reducers 等。如果你使用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'));
    });
  }
};

where/ever/this/is/epics/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)
  );

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

但是请注意,当涉及到诸如require('path/to/epics/epic3').epic3. 您的 epic3 是否实际导出为命名属性?我想是这样,但我注意到您正在这样做done(null, require('./components/SomePath')),如果您使用export defaultES2015/ES6 模块导出该组件,我的直觉告诉我可能无法正常工作。如果这是真的,它实际上是在require('./components/SomePath').default--note 中找到的.default!因此,虽然我的代码是一般的想法,但您应该特别注意所需的路径、导出的属性等。您说它似乎正在工作,所以您的设置可能不同(或者我错了呵呵)


  1. 如果我想控制台记录哪些史诗已添加到 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);
  }
};

但是您也可以.do()在您的epic$主题上使用 RxJS 运算符更具体地执行此操作:

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

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

于 2016-10-24T03:49:58.467 回答