视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
详解在React中跨组件分发状态的三种方法
2020-11-27 22:10:25 责编:小采
文档


当我问自己第一百次时,我正在研究一个典型的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"));

看起来不错,但是他不起作用。

我们正在创建组件实例时初始化<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>
 );
 }
}

//...

现在它可以工作,但对我来说,提升 <BookForm /> 的状态感觉不对。在用户单击“保存”之前, <BookApp /> 不关心对书的任何更改,那么为什么需要将其保持在自己的状态?

方法2:同步state

现在它可以工作,但对我来说,提升<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>
 );
 }
}
//...

这种方法通常被认为是一种不好的做法,因为它违背了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>
 );
 }
}
//...

如果元素具有与上一个渲染不同的键,则React会为其创建一个新实例。因此,当用户选择新书时,<BookForm />的键更改,将创建组件的新实例,并从props初始化状态。

有什么收获?重用组件实例意味着更少的DOM突变,这意味着更好的性能。因此,当我们强制React创建组件的新实例时,我们会为额外的DOM突变获得一些开销。但是对于这样的情况,这种开销是最小的,其中密钥没有变化太快而且组件不大。

下载本文
显示全文
专题