State in redux/react app has a property with the name of the reducer

前端 未结 2 562
自闭症患者
自闭症患者 2021-02-01 07:01

I am creating an app using Redux and React. I run into a problem where I cannot map state to component properties since the state has a property that matches the name of the red

2条回答
  •  余生分开走
    2021-02-01 07:28

    If you only have a single reducer, you don’t need combineReducers(). Just use it directly:

    const initialState = {
      sources: [],
      left: {},
      right: {}
    }
    function app(state = initialState, action) {
      switch (action.type) {
      case 'ADD_SOURCE':
        return Object.assign({}, state, {
          sources: [...state.sources, action.newSource]
        })
      case 'ADD_SOURCE_TO_LEFT':
        return Object.assign({}, state, {
          left: Object.assign({}, state.left, {
            [action.sourceId]: true
          })
        })
      case 'ADD_SOURCE_TO_RIGHT':
        return Object.assign({}, state, {
          right: Object.assign({}, state.right, {
            [action.sourceId]: true
          })
        })
      default:
        return state
      }
    }
    

    Now you can create a store with that reducer:

    import { createStore } from 'redux'
    const store = createStore(app)
    

    And connect a component to it:

    const mapStateToProps = (state) => ({
      sources: state.sources
    })
    

    However your reducer is hard to read because it update many different things at once. Now, this is the moment you want to split it into several independent reducers:

    function sources(state = [], action) {
      switch (action.type) {
      case 'ADD_SOURCE':
        return [...state.sources, action.newSource]
      default:
        return state
      }
    }
    
    function left(state = {}, action) {
      switch (action.type) {
      case 'ADD_SOURCE_TO_LEFT':
        return Object.assign({}, state, {
          [action.sourceId]: true
        })
      default:
        return state
      }    
    }
    
    function right(state = {}, action) {
      switch (action.type) {
      case 'ADD_SOURCE_TO_RIGHT':
        return Object.assign({}, state, {
          [action.sourceId]: true
        })
      default:
        return state
      }    
    }
    
    function app(state = {}, action) {
      return {
        sources: sources(state.sources, action),
        left: left(state.left, action),
        right: right(state.right, action),
      }
    }
    

    This is easier to maintain and understand, and it also makes it easier to change and test reducers independently.

    Finally, as the last step, we can use combineReducers() to generate the root app reducer instead of writing it by hand:

    // function app(state = {}, action) {
    //   return {
    //     sources: sources(state.sources, action),
    //     left: left(state.left, action),
    //     right: right(state.right, action),
    //   }
    // }
    
    import { combineReducers } from 'redux'
    const app = combineReducers({
      sources,
      left,
      right
    })
    

    There is no large benefit to using combineReducers() instead of writing the root reducer by hand except that it’s slightly more efficient and will likely save you a few typos. Also, you can apply this pattern more than once in your app: it’s fine to combine unrelated reducers into a single reducer several times in a nested way.

    All this refactoring would have no effect on the components.

    I would suggest you to watch my free Egghead course on Redux which covers this pattern of reducer composition and shows how combineReducers() is implemented.

提交回复
热议问题