#reactjs #redux #redux-observable #redux-router
#reactjs #redux #redux-наблюдаемый #redux-router
Вопрос:
При использовании redux-observable с react-router имеет ли смысл асинхронно добавлять новые эпосы в соответствии с инструкциями в документации здесь внутри перехватчиков onEnter react-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
};
…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. В этом случае будет ли epic3 добавляться снова в корневой каталог каждый раз, когда мы переходим к /some-path ?
2. Если бы я хотел консоль.запишите, какие эпики были добавлены в корневой каталог, как бы мне это сделать?
Отредактировано в ответ на @JayPhelps
Могу ли я запросить разъяснения по нескольким пунктам?
-
registerEpic fn отлично работает. Я использовал оператор .distinct() для решения проблемы с регистрацией дубликатов epic следующим образом:
export const epic$ = new BehaviorSubject(combineEpics(epic1, epic2)).distinct()
Является ли это одинаково допустимым / хорошим способом обработки регистрации lazy epic, и если нет, не могли бы вы, пожалуйста, объяснить, почему?
- Я использую create-react-app, в котором есть require.assure, а также ES6 import (это разные способы импорта, верно?), И, по сути, я скопировал-вставил пример огромных приложений react-router, в котором был
require.ensure
код, но везде еще я использую инструкции import в верхней части файла.
Таким образом, импорт epic-файлов и registerEpic fn вверху, похоже, работает, при этом ввод путей внутри аргумента require.ensure first также, похоже, работает. Это запутывает ситуацию, если я использую инструкции require.ensure И import? Если я использую require.assure для загрузки ВСЕГО, что требуется маршруту, означает ли это, что я удаляю все инструкции 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' ) )
} )
}
}
-
Regarding registering epics in the ‘getComponent’ fn for async loading — I thought it was actually desirable to have synchronous loading here, so that the route’s component doesn’t render until the epic has been registered. What happens if the user tries to do something on the route, like fetch some detailed info or submit a form, that requires the epic to be registered, and the epic isn’t registered yet?
-
Есть ли какое-либо другое место за пределами объявлений react-router module.export, которое было бы подходящим для ленивой регистрации epics? Поскольку многие маршруты в моем проекте не требуют входа в систему, вход в систему не всегда вызывает изменение маршрута. Однако существует множество epic-файлов, которые должны быть зарегистрированы после входа пользователя в систему. В настоящее время я делаю это действительно глупым и, вероятно, неправильным способом, устанавливая переменную token в моем 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. (Я нашел ваш пример входа в систему на github, так что он может показаться знакомым). Это правильно, или я полностью сбит с толку? Я знаю .concat () синхронен, и я знаю, что redux синхронен — я не уверен, что registerEpics () синхронен, но использование reducer для регистрации epics, похоже, работает нормально — означает ли это, что 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:
В этом случае будет ли epic3 добавляться снова в корневой каталог каждый раз, когда мы переходим к /some-path?
Если вы используете react-router и выполняете разделение кода, вы на самом деле захотите добавить необходимые эпик внутри вашего getComponent
перехватчика, а не в onEnter
хук. Это потому, что getComponent
перехват позволяет асинхронно загружать ресурсы, необходимые для этого маршрута, не только компонент, но также любые epic-файлы, редукторы и т.д. Если вы используете 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)
);
Я создал функцию регистрации, которая гарантирует, что вы не добавляете epic, который уже был добавлен; react-router будет вызываться getComponent
каждый раз, когда вы вводите этот маршрут, поэтому это важно, чтобы у вас не было двух запущенных экземпляров одного и того же epic.
Однако обратите внимание, что мой код делает некоторые предположения, которые могут быть ошибочными из коробки, когда дело доходит до таких вещей, как require('path/to/epics/epic3').epic3
. Ваш epic3 действительно экспортируется как именованное свойство? Я предполагаю это, но я заметил, что вы делаете это, done(null, require('./components/SomePath'))
что, как мне подсказывает интуиция, может работать некорректно, если вы экспортируете этот компонент, используя export default
модули ES2015 / ES6. Если это правда, то это действительно найдено по адресу require('./components/SomePath').default
— обратите .default
внимание! Итак, хотя мой код — это общая идея, вам следует обратить особое внимание на требуемые пути, экспортируемые свойства и т.д. Вы сказали, что это, кажется, работает, так что, возможно, ваша настройка отличается (или я просто ошибаюсь, хе-хе)
- Если бы я хотел утешить.запишите, какие эпики были добавлены в корневой каталог, как бы мне это сделать?
Проще всего было бы просто войти в 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()
оператор RxJS в вашей epic$
теме:
export const rootEpic = (action$, store) =>
epic$
.do(epic => console.log(`new epic added: ${epic.name || '<anonymous>'}`))
.mergeMap(epic =>
epic(action$, store)
);
Однако это будет зарегистрировано <anonymous>
в первый раз, потому что первое, что приходит, является результатом combineEpics(...epicRegistry)
объединения нескольких эпосов в один анонимный эпос.
Комментарии:
1. Спасибо! Я отредактировал свой ответ выше, чтобы запросить дополнительные разъяснения, но я действительно ценю, что вы нашли время для предоставления этого ответа, и это определенно работает.
2. @джей-фелпс, что вы рекомендуете делать
epic$.next(combineEpics(...epicRegistry))
при регистрации нового эпоса? Кажется, что если я этого не сделаю, существующие эпосы никогда не попадут.3. @jay-phelps Спасибо, очень полезно.