redux-observable TypeError: Cannot read property 'type' of undefined

redux-observable TypeError: Cannot read property 'type' of undefined

我一直在尝试实现 react server-side-rendering using next, and i am using the with-redux-observable-app example,该示例工作正常,但我想通过

稍微改进项目
  1. redux modular pattern
  2. fractal project structure
  3. 如果可能的话,我想实现stateless components
  4. 因为#2,我不能再使用react state lifecycle, to solve that i usually took advantage of react router onEnter props, but this建议我应该使用componentWillMount,这不符合我的#2条件

我已经把项目放在github, with this particular problem committed on this branch

这是我到目前为止所做的总结

实现#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)

对于上面的那些,获取工作正常,但是...

我不希望提取保持 运行ning,我希望它 运行 一次 onEnter.

作为实现这一目标的尝试,我写了一个名为 startFetchingCharactersEpic() 的史诗和一个名为 startFetchingCharacters() 的动作,最后在 fetchCharacterEpic() 的末尾添加 mergeMap(() => of(actions.stopFetchingCharacters())), pipe 个参数,考虑以下场景

  1. 调度actions.startFetchingCharacters(),在容器
  2. 这将触发 startFetchingCharactersEpic()
  3. 这将持续到 types.STOP_FETCHING_CHARACTERS
  4. 将调度 actions.fetchCharacter()
  5. 这将触发 fetchCharacterEpic()
  6. 将调度 actions.stopFetchingCharacters()
  7. 这将触发#3

设置初始字符

// ./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

尝试用谷歌搜索问题,但没有找到与我的问题相关的内容

更新

我设法做到了 work again based on @jayphelps' 下面,这让我回到了我原来的一些问题,这些问题是

  1. 如何在不利用 react state lifecycle 的情况下充分使用无状态组件,尤其是替换 onEnter
  2. 如何在页面加载时只调用一次 fetchCharacterEpic

但我猜这 2 个值另一个 post,因为我意识到我在这个问题上问了太多问题 post

这里完全是猜测,但错误可能来自于您在这里发送 Promise:

const resultAction = await rootEpics(
  epic,
  id,
).toPromise()
dispatch(resultAction)

你的问题没有提到,但这意味着你必须有拦截承诺的中间件,因为 redux(和 redux-observable)只需要 POJO { type: string }.

也有可能 promise 没有解决 undefined 以外的任何问题,在这种情况下,您的 epics 中的 ofType 运算符将会阻塞,因为它只适用于那些POJO 操作 { type: string }.


抱歉,我无法提供更具体的帮助,很难理解其意图。

例如这个 await rootEpics(epic, id) 看起来很奇怪,因为 rootEpics 是根史诗并且期望参数是 (action$, store) 并且 UI 组件不应该直接调用 epics?