React删除项目后,列表渲染错误的数据

17
我是一名有用的助手,可以为您翻译文本。
我有一个简单的学生对象列表,其中包含姓名和他们在州内的分数。
他们的姓名绑定到<b>{student.name}</b>,他们的分数也同样绑定。
<input type="text" defaultValue={student.score}/>

每当我想要从这个列表中删除第一个学生并通过调用set state重新呈现组件时。
第二个学生的输入标记显示第一个学生的分数,而不是拥有自己的分数。为什么会发生这种情况,我做错了什么?? 这是我的代码jsbin
class App extends React.Component{
  constructor(){
    super();
    this.state ={
      students:[{name:"A",score:10},{name:"B",score:20},{name:"C",score:30}]
    }
  }
  
  onDelete(index){
    this.state.students.splice(index,1);
    this.setState(this.state);
  }
  
   render(){
     return(
       <div>
         {this.state.students.map((student,index)=>{
              return(
                <div key={index}>
                    <b>{student.name}</b> - <input type="text" defaultValue={student.score}/>
                     <button onClick={this.onDelete.bind(this,index)}>delete</button>
                </div>                
              )
         })}
       </div>
     )
   }
}


ReactDOM.render(<App/>,document.getElementById("main"));
3个回答

43

这是因为您使用的是key={index},而不是对学生唯一的值。

当数组被修改后,索引后面的学生将具有错误的键,React将无法注册密钥更改以使用更新的数据进行重新渲染。

相反,您应该使用类似于以下内容...

<div key={student.name}>

假设 student.name 是唯一的。


非常感谢。但为什么它适用于除 input type="text" 之外的任何其他标记?? - manas
除了 input 标签(如 span)或其他输入类型,还有其他标签吗? - BradByte
1
defaultValue改变时,它不会更新...你需要丢弃该元素并重新生成它(即更改key)...而另一个元素可能会正常工作。如果你使用了value,它可能会起作用...但是除非你给它一个onChange处理程序,否则你只能拥有一个只读输入。 - BradByte
我为我正在初始化的对象生成了随机的6位数字作为ID,将其设置为迭代时的键,这解决了我的问题。 - Nik Hammer-Ellis
这个答案非常有帮助! - John Aldrin

3

在使用键值时,最好使用唯一标识符而不是索引。如果您的JSON没有提供唯一ID,则可以使用像uuid npm模块这样的工具为您生成它。这是链接:https://www.npmjs.com/package/uuid

然后,您可以导入并按如下方式使用它:

import { v4 } from 'uuid'; //there are 5 versions. You can use any.

然后通过调用v4函数来使用它生成唯一的ID,如下所示。
id={v4()}

如果API没有提供唯一标识符,则生成一个是个好主意,但你应该在呈现之前做。如果像你在回答中建议的那样在呈现期间执行,那么实际上每次都会不断重新创建ID,因此每次更新时都会得到新的ID。这会导致不必要的呈现,如果有输入字段,则每次输入字符都会失去焦点。 - earthling
我本意是要生成ID,谢谢你指出来。我已经编辑了我的回答。 - Aswin K
不好的想法,如果您继续分配新的键,将会破坏子组件状态。 - lawrence-witt

0

最佳实践

说实话,您不应直接更改状态数据。您应该先克隆,然后像这样完成您的任务。

onDelete(index) {
    const newStudents = [...this.state.students];
    newStudents.splice(index, 1);
    this.setState({ students: newStudents });
}

为什么我不能直接修改组件的状态?

原始状态会遭到篡改,从而干扰或无法进行两个状态的浅层比较和合并,因为现在你只有一个状态了。这会破坏所有React的生命周期方法。


你的问题和解决方案

  1. 如果你使用了输入 readOnly,你应该将 defaultValue 改为 value,这样你的代码就能正常工作了。
<input type="text" value={student.score} readOnly />

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      students: [
        { name: "A", score: 10 },
        { name: "B", score: 20 },
        { name: "C", score: 30 }
      ]
    };
  }

  onDelete(index) {
    const newStudents = [...this.state.students];
    newStudents.splice(index, 1);
    this.setState({ students: newStudents });
  }

  render() {
    return (
      <div>
        {this.state.students.map((student, index) => {
          return (
            <div key={index}>
              <b>{student.name}</b> -{" "}
              <input type="text" value={student.score} readOnly />
              <button onClick={this.onDelete.bind(this, index)}>delete</button>
            </div>
          );
        })}
      </div>
    );
  }
}

ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

<div id="app"></div>

  1. 否则,您应该提供唯一的key。如果student.name不是唯一的,您可以像这样随机生成GUID
const getGUID = () => "id" + Math.random().toString(16).slice(2);
>  <div key={getGUID()}>

const getGUID = () => "id" + Math.random().toString(16).slice(2);

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      students: [
        { name: "A", score: 10 },
        { name: "B", score: 20 },
        { name: "C", score: 30 }
      ]
    };
  }

  onDelete(index) {
    const newStudents = [...this.state.students];
    newStudents.splice(index, 1);
    this.setState({ students: newStudents });
  }

  render() {
    return (
      <div>
        {this.state.students.map((student, index) => {
          return (
            <div key={getGUID()}>
              <b>{student.name}</b> -{" "}
              <input type="text" defaultValue={student.score} />
              <button onClick={this.onDelete.bind(this, index)}>delete</button>
            </div>
          );
        })}
      </div>
    );
  }
}

ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

<div id="app"></div>

然而,在这种情况下,不稳定的键会导致性能问题,因为组件实例 - 虚拟 DOM 和 DOM 节点 - 实际 DOM 将总是被不必要地重新创建。

简而言之,根据您的数据和行为,您可以选择正确的方式来完成它。


在每次渲染时生成新的密钥不仅会导致性能问题,还会引入错误。我根本不建议将其作为解决方案。 - lawrence-witt

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