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

IT技术 reactjs redux redux-observable redux-router
2021-05-17 11:03:40

当使用终极版,可观察到有react的路由器,这将是有意义的异步的文档中添加新的史诗按照指示在这里中路由变化中的OnEnterreact-router的钩子?

./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. 如果我想在 console.log 中记录哪些史诗被添加到 rootEpic 中,我该怎么做?


编辑以回应@JayPhelps

我可以要求澄清几点吗?

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

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

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

  1. 我使用的创建-react-应用程序,它具有require.ensure以及ES6进口(它们是进口权的不同方式?),基本上,我复制粘贴react,路由器的巨大的应用程式例如其中有require.ensure代码,但无处不在否则,我在文件顶部使用 import 语句。

因此,在顶部导入史诗和 registerEpic fn 似乎有效,同时将路径放在 require.ensure 第一个参数中似乎也有效。如果我使用 require.ensure AND 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' ) )
        } )
    }
}
  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 将是一个更好的地方来注册史诗,而不是减速器。(我在 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
                }))
        );
1个回答

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

如果您使用的react-router和做代码分裂,你居然要添加您的内部必要的史诗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'));
    });
  }
};

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 module导出该组件,则可能无法正常工作如果这是真的,它实际上是在require('./components/SomePath').default--note the .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);
  }
};

但是您也可以.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)将多个史诗组合成一个匿名史诗的结果。