在React中跨组件分发状况的三种要领
当我问本身第一百次时,我正在研讨一个典范的CRUD屏幕:“我应当将状况保留在这个组件中照样将其移动到父组件?”。
假如须要对子组件的状况举行细微掌握。您能够也遇到了一样的题目。
让我们经由过程一个简朴的例子和三种修复要领来回忆它。前两种要领是罕见的做法,第三种要领不太通例。
题目;
为了向您展现我的意义,我将运用一个简朴的书本CRUD(译者注:增添(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete))屏幕(云云简朴,它没有建立和删除操纵)。
我们有三个组成部分。<BookList />是一个组件,显现了用于编辑它们的书本和按钮列表。<BookForm />有两个输入和一个按钮,用于保留对书本的变动。以及包括其他两个组件的<BookApp />。
那末,我们的状况是什么?好吧,<BookApp />应当跟踪书本清单以及辨认当前正在编辑的书本的内容。 <BookList />没有任何状况。而且<BookForm />应当坚持输入的当前状况,直到单击“保留”按钮。
import React, { Component } from "react";
import { render } from "react-dom";
const books = [
{
title: "The End of Eternity",
author: "Isaac Asimov"
},
//...
];
const BookList = ({ books, onEdit }) => (
<table>
<tr>
<th>Book Title</th>
<th>Actions</th>
</tr>
{books.map((book, index) => (
<tr>
<td>{book.title}</td>
<td>
<button onClick={() => onEdit(index)}>Edit</button>
</td>
</tr>
))}
</table>
);
class BookForm extends Component {
state = { ...this.props.book };
render() {
if (!this.props.book) return null;
return (
<form>
<h3>Book</h3>
<label>
Title:
<input
value={this.state.title}
onChange={e => this.setState({ title: e.target.value })}
/>
</label>
<label>
Author:
<input
value={this.state.author}
onChange={e => this.setState({ author: e.target.value })}
/>
</label>
<button onClick={() => this.props.onSave({ ...this.state })}>
Save
</button>
</form>
);
}
}
class BookApp extends Component {
state = {
books: books,
activeIndex: -1
};
render() {
const { books, activeIndex } = this.state;
const activeBook = books[activeIndex];
return (
<div>
<BookList
books={books}
onEdit={index =>
this.setState({
activeIndex: index
})}
/>
<BookForm
book={activeBook}
onSave={book =>
this.setState({
books: Object.assign([...books], { [activeIndex]: book }),
activeIndex: -1
})}
/>
</div>
);
}
}
render(<BookApp />, document.getElementById("root"));
在codesandbox尝试一下
看起来不错,然则他不起作用。
我们正在建立组件实例时初始化<BookForm />状况,因而,当从列表中挑选另一本书时,父级没法让它晓得它须要变动它。
我们改怎样修复它?
要领1:受控组件
一种罕见的要领是将状况提拔,将<BookForm />转换为受控组件。我们删除<BookForm />状况,将activeBook增加到<BookApp />状况,并向<BookForm />增加一个onChange道具,我们在每次输入时都邑调用它。
//...
class BookForm extends Component {
render() {
if (!this.props.book) return null;
return (
<form>
<h3>Book</h3>
<label>
Title:
<input
value={this.props.book.title}
onChange={e =>
this.props.onChange({
...this.props.book,
title: e.target.value
})}
/>
</label>
<label>
Author:
<input
value={this.props.book.author}
onChange={e =>
this.props.onChange({
...this.props.book,
author: e.target.value
})}
/>
</label>
<button onClick={() => this.props.onSave()}>Save</button>
</form>
);
}
}
class BookApp extends Component {
state = {
books: books,
activeBook: null,
activeIndex: -1
};
render() {
const { books, activeBook, activeIndex } = this.state;
return (
<div>
<BookList
books={books}
onEdit={index =>
this.setState({
activeBook: { ...books[index] },
activeIndex: index
})}
/>
<BookForm
book={activeBook}
onChange={book => this.setState({ activeBook: book })}
onSave={() =>
this.setState({
books: Object.assign([...books], { [activeIndex]: activeBook }),
activeBook: null,
activeIndex: -1
})}
/>
</div>
);
}
}
//...
要领2:同步state
如今它能够事情,但对我来讲,提拔<BookForm />的状况觉得不对。在用户单击“保留”之前,<BookApp />不关心对书的任何变动,那末为何须要将其坚持在本身的状况?
在codesandbox尝试一下
如今它能够事情,但对我来讲,提拔<BookForm />的状况觉得不对。在用户单击“保留”之前,<BookApp />不关心对书的任何变动,那末为何须要将其坚持在本身的状况?
//...
class BookForm extends Component {
state = { ...this.props.book };
componentWillReceiveProps(nextProps) {
const nextBook = nextProps.book;
if (this.props.book !== nextBook) {
this.setState({ ...nextBook });
}
}
render() {
if (!this.props.book) return null;
return (
<form>
<h3>Book</h3>
<label>
Title:
<input
value={this.state.title}
onChange={e => this.setState({ title: e.target.value })}
/>
</label>
<label>
Author:
<input
value={this.state.author}
onChange={e => this.setState({ author: e.target.value })}
/>
</label>
<button onClick={() => this.props.onSave({ ...this.state })}>
Save
</button>
</form>
);
}
}
//...
在codesandbox尝试一下
这类要领通常被认为是一种不好的做法,由于它违犯了React关于具有单一现实泉源的主意。我不确定是这类状况,但是,同步状况并不老是那末轻易。另外,我只管防止运用生命周期要领。
要领3:由Key掌握的组件
但为何我们要接纳旧的状况呢?每次用户挑选一本书时,具有一个全新状况的新实例是否是有意义?
为此,我们须要通知React停止运用旧实例并建立一个新实例。这就是key prop的用处。
//…
class BookApp extends Component {
state = {
books: books,
activeIndex: -1
};
render() {
const { books, activeIndex } = this.state;
const activeBook = books[activeIndex];
return (
<div>
<BookList
books={books}
onEdit={index =>
this.setState({
activeIndex: index
})}
/>
<BookForm
key={activeIndex}
book={activeBook}
onSave={book =>
this.setState({
books: Object.assign([...books], { [activeIndex]: book }),
activeIndex: -1
})}
/>
</div>
);
}
}
//…
在codesandbox尝试一下。
假如元素具有与上一个衬着差别的键,则React会为其建立一个新实例。因而,当用户挑选新书时,<BookForm />的键变动,将建立组件的新实例,并从props初始化状况。
有什么收成?重用组件实例意味着更少的DOM突变,这意味着更好的机能。因而,当我们强迫React建立组件的新实例时,我们会为分外的DOM突变取得一些开支。然则关于如许的状况,这类开支是最小的,个中密钥没有变化太快而且组件不大。
谢谢你的浏览。