【问题标题】:React list rendering wrong data after deleting item删除项目后反应列表呈现错误数据
【发布时间】:2017-04-26 18:53:23
【问题描述】:

我有一个简单的学生对象列表,其中包含名称及其状态分数。

他们的名字绑定到<b>{student.name}</b>,他们的分数绑定到

<input type="text" defaultValue={student.score}/>

每当我想从这个列表中删除第一个学生并且 通过调用 set state 重新渲染组件。

第二个学生的输入标签显示第一个学生的分数,而不是自己的分数。为什么会在我做错的地方发生这种情况??

Here is my code 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"));

【问题讨论】:

    标签: reactjs react-dom


    【解决方案1】:

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

    当数组被修改时,删除索引后的学生将拥有错误的键,React 不会注册键更改以使用更新的数据重新渲染。

    你应该改用这样的东西......

    <div key={student.name}>
    

    假设student.name 是唯一的。

    【讨论】:

    • 非常感谢。但是为什么它可以与 input type="text" 以外的任何其他标签一起使用??
    • 除了input(所以是span)或其他输入类型之外的任何其他标签?
    • defaultValue 在更改时不会更新...您需要删除该元素并重新生成它(即更改 key).. 而另一个元素可能可以正常工作。如果您使用了value,它可能会起作用.. 但是除非您给它一个onChange 处理程序,否则您将只有一个只读输入。
    • 我为正在初始化的对象生成了随机的 6 位数字作为 ID,在迭代时将它们设置为键,这解决了我的问题。
    【解决方案2】:

    使用唯一 id 作为键而不是索引总是更好。如果您的 JSON 不提供唯一 ID,您可以使用类似 uuid npm 模块的东西为您生成它。这是链接https://www.npmjs.com/package/uuid

    然后你可以导入它并如下使用

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

    然后使用它通过调用下面的 v4 函数来生成 uique Id

    id={v4()}
    

    【讨论】:

    • 在 api 未提供时生成唯一 id 是个好主意,但您应该在渲染之前执行此操作。如果您在渲染过程中按照您在答案中的建议进行操作,那么您实际上是在一遍又一遍地不断地重新创建 id,因此每次都有更新,您会得到一个新的 id。这会导致不必要的渲染,如果您有一个输入字段,它将失去对每个字符输入的关注。
    • 我的意思是生成id,感谢收看。编辑了我的答案。
    • 坏主意,如果您继续为其分配新键,您将破坏子组件状态。
    【解决方案3】:

    最佳实践

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

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

    Why can't I directly modify a component's state, really?

    previous state 将被您的突变污染。因此,两个状态的浅比较和合并将是 不安或不会发生,因为你现在只有一个状态。 这将破坏所有 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>

    但是,在这种情况下,不稳定的键将 cause harmful performance,因为组件实例 - Virtual DOM 和 DOM 节点 - actual DOM 将始终不必要地重新创建。

    总之,取决于你的数据和行为,你可以选择正确的方式来完成它。

    【讨论】:

    • 在每次渲染时生成一个新密钥不仅会导致性能问题,还会引入错误。我根本不推荐它作为解决方案。
    猜你喜欢
    • 1970-01-01
    • 2020-10-13
    • 2018-01-14
    • 1970-01-01
    • 2021-12-28
    • 2019-05-05
    • 2023-01-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多