如何正确更新对象的状态(ReactJS + Redux)?

4

所以我有两个输入字段:

  <TextField onChange={this.handleUserUsername.bind(this)}
    value={this.props.userInfo.username}
  />

  <TextField onChange={this.handleUserEmail.bind(this)}
    value={this.props.userInfo.email}
  />

一旦输入,我希望将它们存储在名为“userInfo”的对象状态中,如下所示:
  handleUserUsername(event){
    this.props.actions.updateUsername(event.target.value)
  }

  handleUserEmail(event){
    this.props.actions.updateEmail(event.target.value)
  } 

动作创建器是:

  updateUsername: function(eventValue){
    return {
      type: 'UPDATE_USERNAME',
      username: eventValue
    }
  },

  updateEmail: function(eventValue){
    return {
      type: 'UPDATE_USERNAME',
      email: eventValue
    }
  }

而且减速器是:

function(userInfo={}, action){
  switch(action.type){
    case 'UPDATE_USERNAME':
      return {
        username: action.username
      }

    case 'UPDATE_EMAIL':
      return {
        email: action.email
      }

但是一旦文本输入到用户名文本框中,它会显示在文本框中键入的任何内容,并正确存储“用户名”的“Z”,并记录以下内容(在示例中,键入了“Z”):

enter image description here

当文本“M”被输入到电子邮件TextField中时,用户名状态变为“未定义”,而应该保持为Z,电子邮件状态甚至没有被存储。

enter image description here

在示例的日志中,我希望看到的是:
userInfo: Object
    username: "Z"
    email: "M"

我正在使用以下内容来访问状态和操作:

function mapStateToProps(state) {
  return state
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(actions, dispatch)
  }
}

为什么它没有正确地存储“userInfo”对象的状态?更新这样一个对象的正确方法是什么?最终,我想将“userInfo”对象存储到名为“users”的数组中,其中它将存储所有用户信息。
编辑**
当我在用户名文本字段中输入“q”时,会显示以下内容:

enter image description here

当我在密码文本框中输入“m”时,两者现在都变成未定义:

enter image description here

编辑2 **

const registerReducer = function(userInfo={}, action){
  switch(action.type){
    case 'UPDATE_USERNAME':
      return {
        ...userInfo,
        username: action.username
      }

    case 'UPDATE_EMAIL':
      return {
        ...userInfo,
        email: action.email
      }
2个回答

2
在你的示例中,你忽略了应该返回已存在的状态以及由于你的操作导致的更改,从而改变了先前的状态。
这就是reducer的作用:
动作描述了某件事情发生的事实,但不指定应用程序状态如何响应更改。这是reducer的工作。
你需要做的是始终返回一个新的状态对象,该对象是应用于特定操作的旧状态对象的结果。
而不是:
case 'SOME_ACTION_TYPE':
  return {
    someProperty: 'theNewValue'
  }

你应该做的是:

case 'SOME_ACTION_TYPE':
  return {
    ...state, // notice how I create a new object, but preserve all of the old
    propertyThatIChangedInMyReducer: 'theNewValue' // this will override the old
  }

基本上,你需要取出reducer,对于发生的特定操作应用更改并返回一个新对象,该对象还包含了其余未更改的状态树的其他部分。你错过了返回另一部分的步骤。

如果你有一个密码字段也需要以类似的方式处理,你可以这样做:

case 'UPDATE_PASSWORD':
  return {
    ...state, //preserve current state, apply changes to it below
    password: action.password,
  }; 

非常感谢您的澄清!我采纳了您的建议并尝试实现它,如原始帖子EDIT 2 **中所提供的,但是出现了以下错误:在“...”开始处出现意外的标记(5:8),而且无法弄清楚问题出在哪里。 - Jo Ko
如果可以的话,我更喜欢使用ES6。有没有办法知道我正在使用哪个版本?似乎“...”在某些情况下有效,但在其他情况下无效,所以想要确认一下。另外,我该如何切换到ES6? - Jo Ko
@ElodSzopos “你需要做的是始终返回一个新的状态对象,该对象是应用于特定操作的旧状态对象的减速器的结果。”为什么在减速器中每次都要返回一个新的状态对象?有哪些优点?为什么不只更新当前状态?是因为这样可以访问应用程序的状态历史记录以查看到目前为止所做的所有更改吗? - tonix
1
Redux 的理念是实现单向数据流,使用纯函数的 reducers(无副作用)。改变状态或属性可能会导致应用程序出现问题或错误。Redux 的基本原则不可违背,库依赖于它。你可以获得记录操作、重放操作、时间旅行、可靠的数据源等好处,这使得错误可预测且易于理解。希望这有所帮助。 - Elod Szopos
如果你安装了一个名为Redux DevTools的Chrome扩展程序,它将与之非常兼容。但这是另一个话题,需要另行讨论。 - Elod Szopos
显示剩余2条评论

0

我认为问题在于您创建了一个更新后的状态对象,其中仅包含更改字段的信息。由于 Redux 的 原则 之一,其状态应该是不可变的,每次想要更新它时,都应该创建具有所需更改的新状态对象。

在您的情况下,当创建新的状态对象时,您应该返回两个字段(即使其中一个没有更改):

function(userInfo={}, action){
  switch(action.type){
  case 'UPDATE_USERNAME':
    return {
      username: action.username,
      email: userInfo.email     // userInfo is current state, we should save old value
    }

  case 'UPDATE_EMAIL':
    return {
      username: userInfo.username,  // userInfo is current state, we should save old value
      email: action.email      
    }

通过这种方式,您可以创建一个新状态,其中包含未更改(具有相同值)的字段和已更改(具有新值)的字段。 编辑: 更多示例:
假设您的应用程序状态如下:
oldState = {
    email: "oldValue",
    userName: "oldValue"
}

如果您想更改电子邮件,则您的reducer应以以下方式返回状态对象:

return Object.assign({}, oldState, {
    email: "newValue"
})

上面的代码将基于旧状态生成新的状态对象,并在新对象中更改电子邮件属性。 Object.assign

因此,您的新状态将如下所示:

{
    email: "newValue",
    userName: "oldValue"    
}

在你的例子中,你的reducer返回类似于这样的状态对象:
{
    email: "newValue",
}

因此,Redux 无法理解 userName 的当前值是什么。


我在EDIT **下更新了原始帖子,并添加了新的日志。它显示对象状态电子邮件,但一旦第二个文本字段接收到任何文本,它们两个都变成未定义。 - Jo Ko
但我的答案对你仍然是适用的,正如@ElodSzopos在下面提到的那样,你应该在reducer中每次通过actions进行更改时返回整个状态对象(包括电子邮件、用户名、密码)。 - muphblu
我尝试按照我在EDIT 2中提供的方式实现,但出现了以下错误:Unexpected token (5:8)从'...'开始,无法弄清楚哪里有问题。 - Jo Ko
@JoKo 请尝试我的示例,它不需要ES6标准。Spread operator是在ECMAScript 6中添加的,这是javascript实现的。所有官方redux教程都是基于此版本编写的,你最好弄清楚你使用的版本。 - muphblu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接