对象集合的 React JS 状态更新
React JS state updates for collections of objects
我是 React 的新手,我正在尝试通过构建一个简单的笔记应用程序来练习。据我所知一切进展顺利,但是!我读到不应手动更新状态,因此我正在复制我的状态数组并过滤出删除操作的结果。
但是失败了!相反,如果我控制日志,它 正确地从状态数组 中删除了要删除的元素,但是,当我在副本上调用 setState() 来更新我的视图时,列表错了!
出于某种原因,我的 React 列表总是从页面上直观地删除最后一个元素,然后看起来与我的状态不同步。
该应用程序本身是一个带有嵌套列表和列表项组件的表单容器,它使用表单 class 中的道具进行管理。
我做错了什么?
表格Class
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
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
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}>×</button>
</div>
</li>
);
}
}
尝试使用唯一 ID 之类的东西代替 index
作为 NotesList
中每个 NotesListItem
的 key
。看到这个相关的(实际上可能是重复的):
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 之类的东西来生成 "unique" 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
来处理受控输入,尤其是设置值。为什么不创建一个 属性 on state 来结合简单的 onChange
事件处理程序来处理输入值。这符合 React Forms 文档和 "standard" 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 的动作。
其他答案可能足以解决您的问题。我建议查看以下 article mentioned/linked in the React Keys 文档,讨论使用索引作为键的潜在负面影响。
希望对您有所帮助!
组件的构造函数只运行一次。 React 将重用组件实例并向它们传递新的 props。这里的问题是 NodeListItem 将注释的文本缓存在它自己的本地状态中,并在 render 方法中使用该文本。当它的 Parent 通过 props 向它传递一个新的音符时,它不会使用它。它使用现在陈旧的状态。
子组件通常应该使用父组件传入的 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的固定版本。
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}
>
×
</button>
</div>
</li>
);
}
}
我是 React 的新手,我正在尝试通过构建一个简单的笔记应用程序来练习。据我所知一切进展顺利,但是!我读到不应手动更新状态,因此我正在复制我的状态数组并过滤出删除操作的结果。
但是失败了!相反,如果我控制日志,它 正确地从状态数组 中删除了要删除的元素,但是,当我在副本上调用 setState() 来更新我的视图时,列表错了!
出于某种原因,我的 React 列表总是从页面上直观地删除最后一个元素,然后看起来与我的状态不同步。
该应用程序本身是一个带有嵌套列表和列表项组件的表单容器,它使用表单 class 中的道具进行管理。
我做错了什么?
表格Class
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
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
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}>×</button>
</div>
</li>
);
}
}
尝试使用唯一 ID 之类的东西代替 index
作为 NotesList
中每个 NotesListItem
的 key
。看到这个相关的
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 之类的东西来生成 "unique" 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
来处理受控输入,尤其是设置值。为什么不创建一个 属性 on state 来结合简单的 onChange
事件处理程序来处理输入值。这符合 React Forms 文档和 "standard" 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 的动作。
其他答案可能足以解决您的问题。我建议查看以下 article mentioned/linked in the React Keys 文档,讨论使用索引作为键的潜在负面影响。
希望对您有所帮助!
组件的构造函数只运行一次。 React 将重用组件实例并向它们传递新的 props。这里的问题是 NodeListItem 将注释的文本缓存在它自己的本地状态中,并在 render 方法中使用该文本。当它的 Parent 通过 props 向它传递一个新的音符时,它不会使用它。它使用现在陈旧的状态。
子组件通常应该使用父组件传入的 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的固定版本。
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}
>
×
</button>
</div>
</li>
);
}
}