为什么当将两个或多个相同的对象连续添加到状态时,它们总是生成相同的键?

Why, when adding two or more identical objects to state in a row, they always generate the same key?

我正在学习 React,但遇到了这个问题。我一直在寻找答案,但找不到确切的原因。

当我填写并提交表单时,状态中添加了一个新对象。无论我如何生成随机密钥(例如,在下面的代码中,我使用日期对象和 getTime 方法来生成随机密钥),当我将两个或多个相同的对象连续添加到状态时(一个完全接一个),它们总是生成相同的密钥,与 math.random 和我尝试过的所有其他方法相同。

如果我添加一个对象,然后稍微更改任何数据并添加另一个对象,然后添加与第一个对象相同的对象,一切都会完美无缺。唯一的问题是当我尝试连续添加相同的时。

我所说的相同对象是指它们的所有 {key: value} 对都包含相同的数据并且它们具有相同的键。

我的状态对象是这样的:

state = {
    dmd: [
      {name: 'dmd', age: 69, rich: 'yes', id: 1},
      {name: 'lk', age: 19, rich: 'true', id: 2},
      {name: 'tm', age: 83, rich: 'aye', id: 3}
    ]
  }

请注意,当我已经对这个初始状态进行硬编码时,当我添加一个与第三个对象相同的新对象时,不会发生错误。就是新建的时候。

传递给 addDmd 方法的 newDmd 对象起初没有 id,看起来像这样:

{name: 'john', age: 22, rich: 'yes'}

用于改变状态的方法:

  addDmd = (newDmd) => {
    newDmd.id = new Date().getTime();
    let newArray = [...this.state.dmd, newDmd];
    this.setState({
      dmd: newArray
    });
  }

下图是当key变成一样的时候给大家看的(我的key是'id')。可以看到最后3次相加,key是一样的,从第二次开始重复:

我没有足够的声誉点数来post一张图片,所以这是一张link:https://i.imgur.com/WQrVHlp.png

我想知道:

  1. 为什么会这样
  2. 如何解决这个问题
  3. 关于更改状态和生成唯一键的最佳实践

编辑: 从调用 addDmd 方法的地方添加代码。

class AddDmd extends Component {
    state = {
        name: null,
        age: null,
        rich: null
    };

    handleChange = (e) => {
        this.setState({
            [e.target.id]: e.target.value
        });
    }

    handleSubmit = (e) => {
        e.preventDefault();
        this.props.addDmd(this.state);
    }

    render() {
        return (
            <div>
                <form onSubmit={this.handleSubmit}>
                    <label htmlFor="name">Name:</label>
                    <input type="text" id="name" onChange={this.handleChange}></input>
                    <label htmlFor="age">Age:</label>
                    <input type="text" id="age" onChange={this.handleChange}></input>
                    <label htmlFor="rich">rich?:</label>
                    <input type="text" id="rich" onChange={this.handleChange}></input>
                    <button>Submit</button>
                </form>
            </div>
        )
    }
}

编辑 2:在 addDmd 方法所在的位置添加了 class 的完整代码。

class App extends Component {
  state = {
    dmd: [
      {name: 'dmd', age: 69, rich: 'yes', id: 1},
      {name: 'lk', age: 19, rich: 'true', id: 2},
      {name: 'tm', age: 83, rich: 'aye', id: 3}
    ]
  }

  addDmd = (newDmd) => {
    newDmd.id = new Date().getTime();
    let newArray = [...this.state.dmd, newDmd];
    this.setState({
      dmd: newArray
    });
  }

  render() {
    return (
      <div className="App" >
        <AddDmd addDmd={this.addDmd} />
      </div>
    );
  }
}

您的问题很可能与通过引用意外更改嵌套对象有关。

1.为什么会这样:

我现在只能推测。如果您想分享 addDmd 的用法,我可以确认。我相信 newDmd 是从以前的条目填充的。这意味着做 newDmd.id = 会改变状态中的两个对象。

您可以通过在每次添加新条目时检查 id 来自己确认这一点。新的 id 应该有所不同,因为它是基于时间的,但请注意查看以前的条目是否会随之变化。

2。修复方法:

将您的函数更改为此,以便它对状态进行深度复制:

addDmd = (newDmd) => {
  newDmd.id = new Date().getTime();
  let newArray = [...this.state.dmd.map(nestedObj => ({...nestedObj})), newDmd];
  this.setState({
    dmd: newArray
  });
}

3。最佳实践:

据我所知,您生成唯一键的方法应该可以正常工作。我建议的最佳做法是确保在必要时对状态进行深度复制。

更新

我非常接近。问题是您将 this.state 直接传递给 addDmdthis.state 每次组件重新呈现时都会获得一个新的对象引用,但如果您不更改表单中的任何值并再次提交 - 它不需要重新呈现。这意味着您传递 addDmd 相同的对象引用。

简单修复:

handleSubmit = (e) => {
  e.preventDefault();
  this.props.addDmd({...this.state}); // Create new object out of state
}

现在您可以根据需要多次提交相同的表单,它总是会提交一个新的对象引用,并且不会改变之前提交的内容。

为什么会这样? 我完全同意@Brian Thompsan 提到的原因。

此外,我已经为您的用例创建了一个示例代码,我在单击按钮时添加了具有不同 ID 的相同项目(使用 date/time 创建)。它工作正常,这是代码。

import React from "react";
import Listbox from "./listBox";

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      dmd: [
        { name: "dmd", age: 69, rich: "yes", id: 1 },
        { name: "lk", age: 19, rich: "true", id: 2 },
        { name: "tm", age: 83, rich: "aye", id: 3 }
      ]
    };
  }
  addDmd = () => {
    const newId = new Date().getTime();
    console.log(newId);
    let newArray = [
      ...this.state.dmd,
      { id: newId, name: "new name", age: "new age" }
    ];
    this.setState({
      dmd: newArray
    });
  };
  render() {
    return (
      <>
        <input type="button" onClick={this.addDmd} />
        {this.state.dmd.map(item => (
          <div>
            id: {item.id} name:: {item.name}
          </div>
        ))}
      </>
    );
  }
}
export default App;