【问题标题】:State in redux/react app has a property with the name of the reducerredux/react 应用程序中的 state 有一个带有 reducer 名称的属性
【发布时间】:2016-06-10 14:47:40
【问题描述】:

我正在使用 Redux 和 React 创建一个应用程序。我遇到了一个问题,我无法将状态映射到组件属性,因为状态具有与我使用的减速器名称匹配的属性。

根reducer是用combineReducers方法创建的

const rootReducer = combineReducers({
  appReducer
});

初始状态是

const initialState = {
  sources: [], 
  left: {}, 
  right: {},
  diff: {} 
}

然而在组件函数mapStateToProps:

function mapStateToProps(state) {
  return {
    sources: state.sources
  }
}

state.sourcesundefined,因为state 参数的值是

{
  appReducer: {
    sources: [], 
    left: {}, 
    right: {}, 
    diff: {}
  }
}

这是 redux 的一个特性吗?所以当我使用更多的reducer时,它们都会向state变量添加新属性?还是我这边有什么问题(我从未在 redux 教程中注意到这种行为)。

谢谢

【问题讨论】:

  • 你的代码是正确的state.appReducer. sources你需要reducer名字
  • 假设你有 2,3 个 reducer,每个 reducer 都有 sources 属性
  • 你可以通过state.appReducer. sources和`state.appReducer.2 sources`获取具体的sources
  • 你所描述的是combineReducers所做的一部分。
  • 您需要在 appReducer 中设置“state = initialState.sources”来更新/访问特定状态

标签: javascript reactjs state redux


【解决方案1】:

如果你只有一个 reducer,你就不需要combineReducers()。直接使用即可:

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
  }
}

现在您可以使用该 reducer 创建一个商店:

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

并将组件连接到它:

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

但是,您的 reducer 很难阅读,因为它会同时更新许多不同的内容。现在,this 是您想要将其拆分为多个独立减速器的时刻:

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),
  }
}

这样更容易维护和理解,也更容易独立更改和测试reducer。

最后,作为最后一步,我们可以使用combineReducers()来生成根app reducer,而不是手写:

// 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
})

使用combineReducers() 代替手动编写根化简器并没有太大的好处,只是它的效率稍高一些,并且可能会为您节省一些拼写错误。此外,您可以在您的应用中多次应用此模式:可以以嵌套方式多次将不相关的 reducer 组合成一个 reducer。

所有这些重构都不会对组件产生影响。

我建议您观看我的free Egghead course on Redux,它涵盖了这种reducer 组合 模式,并展示了combineReducers() 是如何实现的。

【讨论】:

  • 感谢 Dan 的解释以及这些视频的链接。
【解决方案2】:

其实我相信你的初始状态应该是:

{
  appReducer: {
    sources: [],
    left: {},
    right: {},
    diff: {}
  }
}

这是因为combineReducers 通过获取reducer 的名称并将其内容映射到该名称来工作。

另外,请注意,但如果您要使用超过 1 个减速器,则减速器的名称应该比 appReducer 更具体,并且(只是我个人的看法)它们不需要这个词reducer。一个典型的应用程序可能如下所示:

combineReducers({
  user: userReducer,
  messages: messagesReducer,
  notifications: notificationsReducer
});

然后,您的状态可以通过以下方式访问:

state.user.email
state.messages[0]

【讨论】:

  • 你是对的。然后我应该相应地更改initialState 对象吗?
  • 这个想法是reducer的每个分支都负责自己的initialState。你的根减速器应该没有初始状态,每个减速器应该只包含它的东西。所以你的appReducer 将是一个带有源、左、右等键的对象。
猜你喜欢
  • 2020-05-31
  • 1970-01-01
  • 2017-08-28
  • 2021-01-06
  • 1970-01-01
  • 1970-01-01
  • 2016-01-29
  • 2017-03-03
  • 2019-12-05
相关资源
最近更新 更多