Я пытался реализовать react рендеринг на стороне сервера, используя далее, и я использую пример с-redux-observable-app, пример работает нормально, но я хотел бы немного улучшить проект, выполнив
- модульный шаблон redux
- фрактальная структура проекта
- Если возможно, я хотел бы реализовать компоненты без сохранения состояния.
- Поскольку № 2, я больше не могу использовать жизненный цикл состояния реакции, чтобы решить эту проблему. я обычно пользовался преимуществами React Router onEnter props, но это предполагает, что я должен используйте
componentWillMount
, что не соответствует моему условию №2
Я разместил проект на github, и эта конкретная проблема зафиксирована на этом филиал
Вот краткое изложение того, что я сделал до сих пор
достичь #1
// ./redux/index.js
...
import rootEpics from './root/epics'
import rootReducers from './root/reducers'
export default function initStore(initialState) {
const epicMiddleware = createEpicMiddleware(rootEpics)
const logger = createLogger({ collapsed: true })
const middlewares = applyMiddleware(thunkMiddleware, epicMiddleware, logger)
return createStore(rootReducers, initialState, middlewares)
}
// ./redux/root/epics.js
import { fetchCharacterEpic, startFetchingCharactersEpic } from '../ducks/Character/epics'
const rootEpics = combineEpics(
fetchCharacterEpic,
startFetchingCharactersEpic,
)
export default rootEpics
// ./redux/root/reducers.js
import { combineReducers } from 'redux'
import Character from '../ducks/Character'
const rootReducers = combineReducers({
Character,
})
export default rootReducers
// ./redux/ducks/Character/index.js
import * as types from './types'
const INITIAL_STATE = {
data: {},
error: {},
id: 1,
}
const Character = (state = INITIAL_STATE, { type, payload }) => {
switch (type) {
case types.FETCH_CHARACTER_SUCCESS:
return {
...state,
data: payload.response,
id: state.id + 1,
}
case types.FETCH_CHARACTER_FAILURE:
return {
...state,
error: payload.error,
}
default:
return state
}
}
export default Character
// ./redux/ducks/Character/types.js
export const FETCH_CHARACTER = 'FETCH_CHARACTER'
export const FETCH_CHARACTER_SUCCESS = 'FETCH_CHARACTER_SUCCESS'
export const FETCH_CHARACTER_FAILURE = 'FETCH_CHARACTER_FAILURE'
export const START_FETCHING_CHARACTERS = 'START_FETCHING_CHARACTERS'
export const STOP_FETCHING_CHARACTERS = 'STOP_FETCHING_CHARACTERS'
// ./redux/ducks/Character/actions.js
import * as types from './types'
export const startFetchingCharacters = () => ({
type: types.START_FETCHING_CHARACTERS,
})
export const stopFetchingCharacters = () => ({
type: types.STOP_FETCHING_CHARACTERS,
})
export const fetchCharacter = id => ({
type: types.FETCH_CHARACTER,
payload: { id },
})
export const fetchCharacterSuccess = response => ({
type: types.FETCH_CHARACTER_SUCCESS,
payload: { response },
})
export const fetchCharacterFailure = error => ({
type: types.FETCH_CHARACTER_FAILURE,
payload: { error },
})
// ./redux/ducks/Character/epics.js
import 'rxjs'
import { of } from 'rxjs/observable/of'
import { takeUntil, mergeMap } from 'rxjs/operators'
import { ofType } from 'redux-observable'
import ajax from 'universal-rx-request'
import * as actions from './actions'
import * as types from './types'
export const startFetchingCharactersEpic = action$ => action$.pipe(
ofType(types.START_FETCHING_CHARACTERS),
mergeMap(() => action$.pipe(
mergeMap(() => of(actions.fetchCharacter())),
takeUntil(ofType(types.STOP_FETCHING_CHARACTERS)),
)),
)
export const fetchCharacterEpic = (action$, id) => action$.pipe(
ofType(types.FETCH_CHARACTER),
mergeMap(() => ajax({
url: 'http://localhost:8010/call',
method: 'post',
data: {
method: 'get',
path: `people/${id}`,
},
})
.map(response => actions.fetchCharacterSuccess(
response.body,
true,
))
.catch(error => of(actions.fetchCharacterFailure(
error.response.body,
false,
)))),
)
для достижения # 2
// ./pages/index/container/index.js
import React from 'react'
import { connect } from 'react-redux'
import { of } from 'rxjs/observable/of'
import rootEpics from '../../../redux/root/epics'
import { fetchCharacter } from '../../../redux/ducks/Character/actions'
import Index from '../component'
const mapStateToProps = state => ({
id: state.Character.id,
})
const mapDispatchToProps = dispatch => ({
async setInitialCharacter(id) {
const epic = of(fetchCharacter({ id }))
const resultAction = await rootEpics(
epic,
id,
).toPromise()
dispatch(resultAction)
},
})
export default connect(mapStateToProps, mapDispatchToProps)((props) => {
props.setInitialCharacter(props.id)
return (<Index />)
})
// ./pages/index/component/index.js
import React from 'react'
import Link from 'next/link'
import Helmet from 'react-helmet'
import Info from '../container/info'
const Index = () => (
<div>
<Helmet
title="Ini index | Hello next.js!"
meta={[
{ property: 'og:title', content: 'ini index title' },
{ property: 'og:description', content: 'ini index description' },
]}
/>
<h1>Index Page</h1>
<Info />
<br />
<nav>
{/* eslint-disable jsx-a11y/anchor-is-valid */}
<Link href="/other"><a>Navigate to other</a></Link><br />
<Link href="/about"><a>Navigate to about</a></Link>
{/* eslint-enable jsx-a11y/anchor-is-valid */}
</nav>
</div>
)
export default Index
// ./pages/index/container/info.js
import { connect } from 'react-redux'
import Info from '../../../components/Info'
const mapStateToProps = state => ({
data: state.Character.data,
error: state.Character.error,
})
export default connect(mapStateToProps)(Info)
с теми, что выше, выборка работает нормально, но...
я не хочу, чтобы выборка продолжалась, я хочу, чтобы она выполнялась только один раз onEnter
.
В попытке добиться этого я написал эпопею под названием startFetchingCharactersEpic()
и действие под названием startFetchingCharacters()
и, наконец, добавил mergeMap(() => of(actions.stopFetchingCharacters())),
в конце аргументов fetchCharacterEpic()
pipe
, имея в виду следующий сценарий.
- отправить action.startFetchingCharacters() в контейнере
- это вызовет
startFetchingCharactersEpic()
- так будет до
types.STOP_FETCHING_CHARACTERS
- который отправит action.fetchCharacter()
- это вызовет
fetchCharacterEpic()
- который отправит action.stopFetchingCharacters()
- это вызовет #3
setInitialCharacter
// ./pages/index/container/index.js
const mapDispatchToProps = dispatch => ({
async setInitialCharacter(id) {
const epic = of(startFetchingCharacters())
const resultAction = await rootEpics(
epic,
id,
).toPromise()
dispatch(resultAction)
},
})
но, сделав это, я получил TypeError: Cannot read property 'type' of undefined
, консоль не дает мне достаточно информации, кроме как сказать, что ошибка исходит от setInitialCharacter
Пробовал гуглить проблему, но не нашел ничего, связанного с моей проблемой
ОБНОВЛЕНИЕ
Мне удалось заставить его снова работать на основе @jayphelps' ответ ниже , что вернуло меня к некоторым из моих первоначальных проблем, которые
- Как полностью использовать компонент без сохранения состояния без использования жизненного цикла состояния реакции, особенно замены
onEnter
- Как просто вызвать
fetchCharacterEpic
только один раз при загрузке страницы
но я думаю, что эти 2 заслуживают отдельного поста, так как я понял, что задаю слишком много вопросов в этом посте.