【问题标题】:React JS state updates for collections of objects对象集合的 React JS 状态更新
【发布时间】:2019-04-11 05:47:21
【问题描述】:

我对 React 很陌生,我正在尝试通过构建一个简单的笔记应用程序来练习。据我所知,一切都很好,但是!我读到状态不应该手动更新,所以我正在复制我的状态数组并过滤掉删除操作的结果。

但它失败了!相反,如果我控制台日志,它会正确地从状态数组中删除要删除的元素,但是,当我在副本上调用 setState() 以更新我的视图时,列表是错误的!

由于某种原因,我的 React 列表总是在视觉上从页面中删除最后一个元素,然后出现与我的状态不同步。

应用程序本身是一个带有嵌套列表和列表项组件的表单容器,它们使用表单类中的道具进行管理。

我做错了什么?

表单类

class NotesForm extends Component {
  constructor(props) {
    super(props);

    const list = [
     { text: "Build out UI" },
     { text: "Add new note" },
     { text: "delete notes" },
     { text: "edit notes" }
   ];

    this.state = {
      'notes': list
    };

    // this.notes = list;
    this.handleSubmit = this.handleSubmit.bind(this);
    this.deleteNote = this.deleteNote.bind(this);
  }

  handleSubmit(e) {
    e.preventDefault();
    if (this.input.value.length === 0) { return; }

    this.state.notes.push({text: this.input.value});
    this.setState({ notes: this.state.notes });

    this.input.value = "";
  }

  // BUG - deletes WRONG note!!
  deleteNote(note) {
    console.log({'DELETE_NOTE': note.text})
    // var list = _.clone(this.state.notes);
    var list = [...this.state.notes];

    var filteredNotes = _.filter(list, function(n) {
      return (n.text !== note.text);
    })

    console.log({
      'list': list,
      'filteredNotes': filteredNotes
    })

    this.setState({ notes: filteredNotes });
  }

  render() {
    return (
      <div className="row notes-form">
        <div className="col-xs-12">
          <form onSubmit={this.handleSubmit}>
            <input type="text" className="new-note-input" ref={(input) => this.input = input} />
            <br />
            <button className="add-btn btn btn-info btn-block" type="button" onClick={this.handleSubmit}>Add</button>
            <br />
            <NotesList notes={this.state.notes} deleteNote={this.deleteNote} />
          </form>
        </div>
      </div>
    );
  }
}

列表类

class NotesList extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <ul className="notes-list">
        {this.props.notes.map((n, index) => <NotesListItem key={index} note={n} deleteNote={this.props.deleteNote} />)}
      </ul>
    );
  }
}

列表项类

class NotesListItem extends Component {
  constructor(props) {
    super(props);

    this.state = {
      'text': props.note.text
    };

    this.delete = this.delete.bind(this);
  }

  delete() {
    this.props.deleteNote(this.props.note);
  }

  render() {
    return (

      <li className="notes-list-item">
        <span className="item-text">{this.state.text}</span>
        <div className="notes-btn-group btn-group" role="group">
          <button className="delete-btn btn btn-danger" type="button" onClick={this.delete}>&times;</button>
        </div>
      </li>

    );
  }
}

【问题讨论】:

  • NotesForm 中未定义变量“list”,请将您的 App 组件添加到您的帖子中。或者更好:使用codesandbox.io 创建您的错误演示并分享链接。
  • 糟糕!我已经编辑并添加了用于“列表”的常量。我也会看看在代码沙箱上设置这个,谢谢!

标签: javascript reactjs state render


【解决方案1】:

尝试使用唯一 ID 代替 index 作为 key 用于 NotesList 中的每个 NotesListItem。看到这个相关的question(实际上可能是重复的):

import React, { Component } from 'react';
import NotesListItem from './NotesListItem';

class NotesList extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <ul className="notes-list">
        {this.props.notes.map((n, index) => <NotesListItem key={n.id} note={n} deleteNote={this.props.deleteNote} />)}
      </ul>
    );
  }
}

export default NotesList;

您可以使用uuid 之类的东西来生成“唯一”ID。您可以通过多种方式生成唯一键,但这取决于您的数据结构。此外,使用唯一 id 并基于 id 进行过滤有助于避免数组中的两个注释具有相同文本的情况,因为基于 text 值的过滤会删除它们。

import uuidv1 from 'uuid/v1';

// ...

handleSubmit(e) {
  e.preventDefault();
  if (this.input.value.length === 0) { return; }

  this.state.notes.push({id: uuidv1(), text: this.input.value});
  this.setState({ notes: this.state.notes });

  this.input.value = "";
}

我只建议使用这样的内容,因为您的文本可能会被复制。你甚至可以使用类似的东西逃脱:

{this.props.notes.map((n, index) => <NotesListItem key={index + n.text} note={n} deleteNote={this.props.deleteNote} />)}

另外,你不应该像this.state.notes.push({text: this.input.value}); 那样直接改变状态。试试这样的:

handleSubmit(e) {
  e.preventDefault();
  if (this.input.value.length === 0) { return; }

  const note = { id: uuidv1(), text: this.input.value };
  const notes = [...this.state.notes, note];

  this.setState({ notes });

  this.input.value = "";
}

另外,我会避免使用ref 来处理受控输入,尤其是设置值。为什么不结合简单的onChange 事件处理程序创建一个处理输入值的状态属性。这将符合 React Forms 文档和处理输入值更新的“标准”React 方式:

handleChange(e) {
  this.setState({ text: e.target.value });
}

handleSubmit(e) {
  e.preventDefault();
  if (this.state.text.length === 0) { return; }

  const note = { id: uuidv1(), text: this.state.text };
  const notes = [...this.state.notes, note];

  this.setState({ text: '', notes });
}

render() {
  // ...
  <input type="text" className="new-note-input" value={this.state.text} onChange={this.handleChange} />
  // ...
}

这里有一个example 在行动。

其他答案可能足以解决您的问题。我建议查看 React Keys 文档中提到/链接的以下 article 讨论使用索引作为键的潜在负面影响。

希望对您有所帮助!

【讨论】:

  • 对于这个小例子,使用索引作为key就可以了。
  • @jkerr838 我添加了一条关于在过滤时使用唯一 ID 的好处的注释。帮助避免删除具有相同潜在/重复文本的其他注释。
  • 完美!这绝对是问题的关键。我试过 (new Date()).getMilliseconds() 但它太快了,我仍然得到重复的键。我尝试了 uuid 包,并且注释删除工作完全符合预期。谢谢@AlexanderStaroselsky!
  • 很高兴听到它有帮助。我非常非常建议也执行@jkerr838 的建议,即使用this.props.note.text 而不是将text 的值复制到NotesListItem 中的状态。这是一个重要的区别,他对此的解释非常棒。
【解决方案2】:

组件的构造函数只运行一次。 React 将重用组件实例,向它们传递新的道具。这里的问题是 NodeListItem 将便笺的文本缓存在它自己的本地状态中,并在 render 方法中使用该文本。当它的 Parent 通过 props 传递一个新的 note 给它时,它不会使用它。它使用现在陈旧的状态。

子组件通常应该使用 Parent 传入的 props 中的数据。

class NotesListItem extends Component {
  constructor(props) {
    super(props);

    // The problem is this statement here
    this.state = {
      'text': props.note.text
    };

    this.delete = this.delete.bind(this);
  }
}

这是NotesListItem 类的固定版本。

class NotesListItem extends Component {
  constructor(props) {
    super(props);

    this.delete = this.delete.bind(this);
  }

  delete() {
    this.props.deleteNote(this.props.note);
  }

  render() {
    return (
      <li className="notes-list-item">
        <span className="item-text">{this.props.note.text}</span> {/* <-- using props */}
        <div className="notes-btn-group btn-group" role="group">
          <button
            className="delete-btn btn btn-danger"
            type="button"
            onClick={this.delete}
          >
            &times;
          </button>
        </div>
      </li>
    );
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-09-08
    • 2020-07-30
    • 2021-07-30
    • 1970-01-01
    • 1970-01-01
    • 2018-03-20
    • 1970-01-01
    相关资源
    最近更新 更多