在 react-router onEnter 钩子中懒惰地添加新的 epics 是一种有效的做法吗?
Is it an efficient practice to add new epics lazily inside react-router onEnter hooks?
将 redux-observable 与 react-router 一起使用时,按照 here 文档 here 中 [= 的 onEnter 挂钩中的说明异步添加新的 epics 是否有意义68=] 路线变更期间?
./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
};
...routes/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 其中 epics 添加到 rootEpic 中,我该怎么做?
编辑以回应@JayPhelps
我可以要求澄清几点吗?
registerEpic fn 效果很好。我一直在使用 .distinct() 运算符来解决重复的史诗注册问题,如下所示:
export const epic$ = new BehaviorSubject(combineEpics(epic1, epic2)).distinct()
这是处理惰性史诗注册的同样 valid/good 方式吗?如果不是,您能解释一下原因吗?
- 我正在使用 create-react-app,它具有 require.ensure 以及 ES6 导入(它们是不同的导入方式,对吗?),基本上,我 copy-pasted react-router's huge-apps example其中有
require.ensure
代码,但在其他任何地方,我都在文件顶部使用导入语句。
所以在顶部导入 epics 和 registerEpic fn 似乎可行,而将路径放在 require.ensure 第一个参数中似乎也可行。如果我使用 require.ensure AND import 语句,它会把事情搞得一团糟吗?如果我使用 require.ensure 来加载路由所需的一切,这是否意味着我删除了该路由组件的嵌套 (route-less) 组件内的所有导入语句?
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' ) )
} )
}
}
关于在 'getComponent' fn 中注册 epics 以进行异步加载 - 我认为在这里进行同步加载实际上是可取的,这样路由的组件就不会呈现直到史诗被注册。如果用户试图在路线上做一些事情,比如获取一些详细信息或提交表格,这需要注册史诗,而史诗尚未注册,会发生什么情况?
除了 react-router module.export 声明之外还有其他地方适合延迟注册 epics 吗?由于我项目中的许多路由不需要登录,因此登录并不总是会触发路由更改。但是,有一堆 epics 应该在用户登录后注册。目前,我通过在我的 authReducer 中设置一个令牌变量,以一种非常愚蠢且可能是错误的方式来做这件事,但它所做的一切是调用注册新 epics:
的函数
'./../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 是注册 epics 的更好地方,而不是 reducer。 (我发现 your login example on github 所以它看起来可能很眼熟)。那是正确的,还是我完全没有根据?我知道 .concat() 是同步的,我知道 redux 是同步的——我不确定 registerEpics() 是否同步,但是使用 reducer 注册 epics SEEMS 可以正常工作——这是否意味着registerEpics 是同步的吗?或者它只是意味着事情发生得太快以至于无关紧要?或者它是否只是由于我对导入语句以及 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
}))
);
In this case, would epic3 get added again to the rootEpic every time we navigate to /some-path?
如果您正在使用 react-router 并进行代码拆分,您实际上需要在 getComponent
挂钩中添加必要的 epics,不是 onEnter
挂钩。这是因为 getComponent
钩子允许异步加载该路由所需的资源,不仅是组件,还有任何 epics、reducer 等。如果你使用 require.ensure()
,这会告诉 webpack将这些项目拆分成一个单独的包,这样它们就不会包含在原始条目中——所以不要将这些文件导入其他任何地方,否则你会否定它!
大致来说,这是一个可能看起来像的例子:
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)
);
我创建了一个注册功能,确保您不会添加已经添加的史诗; react-router 将调用 getComponent
每次 你进入那条路线,所以这很重要,这样你就不会有两个 运行 同一个史诗的实例.
但是请注意,当涉及到 require('path/to/epics/epic3').epic3
之类的事情时,我的代码做了一些假设,这些假设可能是开箱即用的。您的 epic3 实际上是作为命名的 属性 导出的吗?我想是的,但我注意到您正在这样做 done(null, require('./components/SomePath'))
如果您使用 export default
和 ES2015/ES6 模块导出该组件,我的直觉告诉我可能无法正常工作。如果这是真的,它实际上是在 require('./components/SomePath').default
找到的——注意 .default
!所以虽然我的代码是一般的想法,但你应该特别注意需要的路径、导出的属性等。你说它似乎有效,所以也许你的设置不同(或者我错了嘿嘿)
- If I wanted to console.log which epics have been added into the rootEpic, how would I do that?
最简单的方法就是登录 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);
}
};
但您也可以在 epic$
主题上使用 RxJS .do()
运算符更具体地做到这一点:
export const rootEpic = (action$, store) =>
epic$
.do(epic => console.log(`new epic added: ${epic.name || '<anonymous>'}`))
.mergeMap(epic =>
epic(action$, store)
);
然而,这将记录 <anonymous>
第一次,因为第一个通过的是 combineEpics(...epicRegistry)
的结果,它将多个 epics 组合成一个匿名史诗。
将 redux-observable 与 react-router 一起使用时,按照 here 文档 here 中 [= 的 onEnter 挂钩中的说明异步添加新的 epics 是否有意义68=] 路线变更期间?
./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
};
...routes/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 其中 epics 添加到 rootEpic 中,我该怎么做?
编辑以回应@JayPhelps
我可以要求澄清几点吗?
registerEpic fn 效果很好。我一直在使用 .distinct() 运算符来解决重复的史诗注册问题,如下所示:
export const epic$ = new BehaviorSubject(combineEpics(epic1, epic2)).distinct()
这是处理惰性史诗注册的同样 valid/good 方式吗?如果不是,您能解释一下原因吗?
- 我正在使用 create-react-app,它具有 require.ensure 以及 ES6 导入(它们是不同的导入方式,对吗?),基本上,我 copy-pasted react-router's huge-apps example其中有
require.ensure
代码,但在其他任何地方,我都在文件顶部使用导入语句。
所以在顶部导入 epics 和 registerEpic fn 似乎可行,而将路径放在 require.ensure 第一个参数中似乎也可行。如果我使用 require.ensure AND import 语句,它会把事情搞得一团糟吗?如果我使用 require.ensure 来加载路由所需的一切,这是否意味着我删除了该路由组件的嵌套 (route-less) 组件内的所有导入语句?
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' ) )
} )
}
}
关于在 'getComponent' fn 中注册 epics 以进行异步加载 - 我认为在这里进行同步加载实际上是可取的,这样路由的组件就不会呈现直到史诗被注册。如果用户试图在路线上做一些事情,比如获取一些详细信息或提交表格,这需要注册史诗,而史诗尚未注册,会发生什么情况?
除了 react-router module.export 声明之外还有其他地方适合延迟注册 epics 吗?由于我项目中的许多路由不需要登录,因此登录并不总是会触发路由更改。但是,有一堆 epics 应该在用户登录后注册。目前,我通过在我的 authReducer 中设置一个令牌变量,以一种非常愚蠢且可能是错误的方式来做这件事,但它所做的一切是调用注册新 epics:
的函数'./../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 是注册 epics 的更好地方,而不是 reducer。 (我发现 your login example on github 所以它看起来可能很眼熟)。那是正确的,还是我完全没有根据?我知道 .concat() 是同步的,我知道 redux 是同步的——我不确定 registerEpics() 是否同步,但是使用 reducer 注册 epics SEEMS 可以正常工作——这是否意味着registerEpics 是同步的吗?或者它只是意味着事情发生得太快以至于无关紧要?或者它是否只是由于我对导入语句以及 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
}))
);
In this case, would epic3 get added again to the rootEpic every time we navigate to /some-path?
如果您正在使用 react-router 并进行代码拆分,您实际上需要在 getComponent
挂钩中添加必要的 epics,不是 onEnter
挂钩。这是因为 getComponent
钩子允许异步加载该路由所需的资源,不仅是组件,还有任何 epics、reducer 等。如果你使用 require.ensure()
,这会告诉 webpack将这些项目拆分成一个单独的包,这样它们就不会包含在原始条目中——所以不要将这些文件导入其他任何地方,否则你会否定它!
大致来说,这是一个可能看起来像的例子:
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)
);
我创建了一个注册功能,确保您不会添加已经添加的史诗; react-router 将调用 getComponent
每次 你进入那条路线,所以这很重要,这样你就不会有两个 运行 同一个史诗的实例.
但是请注意,当涉及到 require('path/to/epics/epic3').epic3
之类的事情时,我的代码做了一些假设,这些假设可能是开箱即用的。您的 epic3 实际上是作为命名的 属性 导出的吗?我想是的,但我注意到您正在这样做 done(null, require('./components/SomePath'))
如果您使用 export default
和 ES2015/ES6 模块导出该组件,我的直觉告诉我可能无法正常工作。如果这是真的,它实际上是在 require('./components/SomePath').default
找到的——注意 .default
!所以虽然我的代码是一般的想法,但你应该特别注意需要的路径、导出的属性等。你说它似乎有效,所以也许你的设置不同(或者我错了嘿嘿)
- If I wanted to console.log which epics have been added into the rootEpic, how would I do that?
最简单的方法就是登录 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);
}
};
但您也可以在 epic$
主题上使用 RxJS .do()
运算符更具体地做到这一点:
export const rootEpic = (action$, store) =>
epic$
.do(epic => console.log(`new epic added: ${epic.name || '<anonymous>'}`))
.mergeMap(epic =>
epic(action$, store)
);
然而,这将记录 <anonymous>
第一次,因为第一个通过的是 combineEpics(...epicRegistry)
的结果,它将多个 epics 组合成一个匿名史诗。