Is it an efficient practice to add new epics lazily inside react-router onEnter hooks?

老子叫甜甜 提交于 2019-11-30 23:41:36

In this case, would epic3 get added again to the rootEpic every time we navigate to /some-path?

If you're using react-router and doing code splitting, you'll actually want to add the necessary epics inside your getComponent hook, not the onEnter hook. This is because the getComponent hook allows async loading of resources necessary for that route, not just the component but also any epics, reducers, etc. If you use require.ensure(), this tells webpack to split these items into a separate bundle so they are not included in the original entry one--so don't import those files anywhere else, otherwise you'll negate this!

So roughly this is an example of what that might look like:

routes/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)
  );

I created a registration function that makes sure you're not adding an epic that already has been added; react-router will call getComponent every time you enter that route, so this is important so you don't have two running instances of the same epic.

Take note however, my code makes some assumptions that might be wrong out of box, when it comes to things like require('path/to/epics/epic3').epic3. Is your epic3 actually exported as a named property? I presume so, but I noticed you are doing this done(null, require('./components/SomePath')) which my gut tells me might not be working correctly if you're exporting that component using export default with ES2015/ES6 modules. If that's true, it's actually found at require('./components/SomePath').default--note the .default! So while my code is the general idea, you should take special note of the require paths, exported properties, etc. You said it seems to be working, so perhaps your setup is different (or I'm just wrong hehe)


  1. If I wanted to console.log which epics have been added into the rootEpic, how would I do that?

The easiest would just be to log inside the registerEpic utility:

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);
  }
};

But you can also do this more concretely using the RxJS .do() operator on your epic$ subject:

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

However, this will log <anonymous> the first time, because the first one that comes through is the result of combineEpics(...epicRegistry) which combines multiple epics into a single anonymous epic.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!