redux-observable TypeError:无法读取属性' type'未定义的

时间:2018-02-12 12:11:06

标签: redux-observable

我一直在尝试使用react实现next服务器端呈现,而我使用with-redux-observable-app example,示例工作正常,但我想改进项目一点点做

  1. redux modular pattern
  2. fractal project structure
  3. 如果可能,我想实施stateless components
  4. 因为#2,我不能再使用react state lifecycle来解决我通常利用反应路由器onEnter道具,但是this建议我应该使用componentWillMount,这不会#39;满足我的#2条件
  5. 我已将项目放在github上,并在此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)
    

    与上述相关,获取工作正常,但......

    我不希望抓取继续运行,我希望它只运行一次onEnter

    为了实现这一目标,我写了一部名为startFetchingCharactersEpic()的史诗,以及一部名为startFetchingCharacters()的动作,最后在mergeMap(() => of(actions.stopFetchingCharacters())), {{{{}} {{} 1}}参数,记住以下场景

    1. 在容器中发送actions.startFetchingCharacters()
    2. 会触发fetchCharacterEpic()
    3. 将在pipe
    4. 之前执行此操作
    5. 将发送actions.fetchCharacter()
    6. 会触发startFetchingCharactersEpic()
    7. 将调度actions.stopFetchingCharacters()
    8. 将触发#3
    9. setInitialCharacter

      types.STOP_FETCHING_CHARACTERS

      但是通过这样做我得到fetchCharacterEpic(),控制台没有提供足够的信息,而不是说错误来自// ./pages/index/container/index.js const mapDispatchToProps = dispatch => ({ async setInitialCharacter(id) { const epic = of(startFetchingCharacters()) const resultAction = await rootEpics( epic, id, ).toPromise() dispatch(resultAction) }, })

      尝试使用Google搜索问题,但发现与我的问题无关

      更新

      我设法根据下面的work again @jayphelps'设置answer,这让我回到原来的一些问题,

      1. 如何在不使用react state lifecycle的情况下完全使用无状态组件,尤其是替换TypeError: Cannot read property 'type' of undefined
      2. 如何在页面加载时只调用一次setInitialCharacter
      3. 但我猜这两个值得另一篇文章,因为我意识到我在这篇文章上提出了太多问题

1 个答案:

答案 0 :(得分:1)

在这里完全猜测,但错误可能来自于你在这里发送承诺的事实:

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

您的问题没有提及,但这意味着您必须拥有截取该承诺的中间件,因为redux(和redux-observable)仅预期POJO { type: string }

承诺也有可能不会解决undefined以外的任何内容,在这种情况下,史诗中的ofType运算符会窒息,因为它只适用于那些POJO操作{{1} }。

对不起,我不能更具体地说,很难遵循意图。

e.g。这个{ type: string }看起来很奇怪,因为rootEpics是根史诗,并期望参数为await rootEpics(epic, id),UI组件不应该直接调用史诗?