详解在React中跨组件分发状态的三种方法


Posted in Javascript onAugust 09, 2018

当我问自己第一百次时,我正在研究一个典型的CRUD屏幕:“我应该将状态保留在这个组件中还是将其移动到父组件?”。

如果需要对子组件的状态进行轻微控制。您可能也遇到了同样的问题。

让我们通过一个简单的例子和​​三种修复方法来回顾它。前两种方法是常见的做法,第三种方法不太常规。

问题;

为了向您展示我的意思,我将使用一个简单的书籍CRUD(译者注:增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete))屏幕(如此简单,它没有创建和删除操作)。

详解在React中跨组件分发状态的三种方法

我们有三个组成部分。 <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突变获得一些开销。但是对于这样的情况,这种开销是最小的,其中密钥没有变化太快而且组件不大。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery AJAX 调用WebService实现代码
Mar 24 Javascript
jQuery学习笔记之DOM对象和jQuery对象
Dec 22 Javascript
JS中的this变量的使用介绍
Oct 21 Javascript
理解Javascript闭包
Nov 01 Javascript
Javascript中Array用法实例分析
Jun 13 Javascript
浅谈jQuery中的checkbox问题
Aug 10 Javascript
Vue中父组件向子组件通信的方法
Jul 11 Javascript
vue cli使用绝对路径引用图片问题的解决
Dec 06 Javascript
JS+CSS实现滚动数字时钟效果
Dec 25 Javascript
微信小程序实现MUI数字输入框效果
Jan 31 Javascript
iview通过Dropdown(下拉菜单)实现的右键菜单
Oct 26 Javascript
javascript单张多张图无缝滚动实例代码
May 10 Javascript
使用jquery DataTable和ajax向页面显示数据列表的方法
Aug 09 #jQuery
React中如何引入Angular组件详解
Aug 09 #Javascript
jQuery 实现批量提交表格多行数据的方法
Aug 09 #jQuery
详解js的视频和音频采集
Aug 09 #Javascript
Angular中的ng-template及angular 使用ngTemplateOutlet 指令的方法
Aug 08 #Javascript
深入理解Promise.all
Aug 08 #Javascript
vue js秒转天数小时分钟秒的实例代码
Aug 08 #Javascript
You might like
php 前一天或后一天的日期
2008/06/28 PHP
PHP 关于访问控制的和运算符优先级介绍
2013/07/08 PHP
PHP+Mysql+Ajax+JS实现省市区三级联动
2014/05/23 PHP
php获取网页请求状态程序示例
2014/06/17 PHP
浅谈PHP Cookie处理函数
2016/06/10 PHP
Event altKey,ctrlKey,shiftKey属性解析
2013/12/18 Javascript
JS函数重载的解决方案
2014/05/13 Javascript
原生javascript实现DIV拖拽并计算重复面积
2015/01/02 Javascript
原生javascript实现图片弹窗交互效果
2015/01/12 Javascript
AngularJS语法详解
2015/01/23 Javascript
jQuery实现分隔条左右拖动功能
2015/11/21 Javascript
Bootstrap+jfinal实现省市级联下拉菜单
2016/05/30 Javascript
Javascript之Math对象详解
2016/06/07 Javascript
javascript使用 concat 方法对数组进行合并的方法
2016/09/08 Javascript
jQuery简单创建节点的方法
2016/09/09 Javascript
详解jQuery中ajax.load()方法
2017/01/25 Javascript
vue源码解析之事件机制原理
2018/04/21 Javascript
vue实现动态添加数据滚动条自动滚动到底部的示例代码
2018/07/06 Javascript
Vue2.0中三种常用传值方式(父传子、子传父、非父子组件传值)
2018/08/16 Javascript
小程序点赞收藏功能的实现代码示例
2018/09/07 Javascript
基于js实现抽红包并分配代码实例
2019/09/19 Javascript
Python的Flask框架应用调用Redis队列数据的方法
2016/06/06 Python
django之常用命令详解
2016/06/30 Python
Python中read()、readline()和readlines()三者间的区别和用法
2017/07/30 Python
Python实现图片转字符画的代码实例
2019/02/22 Python
Python实现的旋转数组功能算法示例
2019/02/23 Python
使用Python进行体育竞技分析(预测球队成绩)
2019/05/16 Python
django云端留言板实例详解
2019/07/22 Python
Pandas 缺失数据处理的实现
2019/11/04 Python
根据tensor的名字获取变量的值方式
2020/01/04 Python
python基于opencv 实现图像时钟
2021/01/04 Python
canvas实现滑动验证的实现示例
2020/08/11 HTML / CSS
2014年科技工作总结
2014/11/26 职场文书
初中班主任教育随笔
2015/08/15 职场文书
文案策划岗位个人自我评价(范文)
2019/08/08 职场文书
女人创业励志语录,句句蕴含能量,激发你的潜能
2019/08/20 职场文书