详解在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 相关文章推荐
js replace正则表达式应用案例讲解
Jan 17 Javascript
检查输入的是否是数字使用keyCode配合onkeypress事件
Jan 23 Javascript
页面图片浮动左右滑动效果的简单实现案例
Feb 10 Javascript
js读取csv文件并使用json显示出来
Jan 09 Javascript
JS实现简单的图书馆享元模式实例
Jun 30 Javascript
Javascript中 带名 匿名 箭头函数的重要区别(推荐)
Jan 29 Javascript
windows下vue-cli及webpack搭建安装环境
Apr 25 Javascript
JS实现静态页面搜索并高亮显示功能完整示例
Sep 19 Javascript
浅析JavaScript中的特殊数据类型
Dec 15 Javascript
javascript实现最长公共子序列实例代码
Feb 05 Javascript
vue: WebStorm设置快速编译运行的方法
Oct 18 Javascript
记一次vue跨域的解决
Oct 21 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/07/30 PHP
用php实现的下载css文件中的图片的代码
2010/02/08 PHP
PHP学习之数组的定义和填充
2011/04/17 PHP
Windows下利用Gvim写PHP产生中文乱码问题解决方法
2011/04/20 PHP
Yii框架模拟组件调用注入示例
2019/11/11 PHP
动态加载js的几种方法
2006/10/23 Javascript
javascript 自定义事件初探
2009/08/21 Javascript
推荐JavaScript实现继承的最佳方式
2014/11/11 Javascript
JS+CSS实现滑动切换tab菜单效果
2015/08/25 Javascript
require.js 加载 vue组件 r.js 合并压缩的实例
2016/10/14 Javascript
jQuery实现百度登录框的动态切换效果
2017/04/21 jQuery
深究AngularJS——ng-checked(回写:带真实案例代码)
2017/06/13 Javascript
angular或者js怎么确定选中ul中的哪几个li
2017/08/16 Javascript
js编写简单的聊天室功能
2017/08/17 Javascript
vue click.stop阻止点击事件继续传播的方法
2018/09/04 Javascript
Python使用代理抓取网站图片(多线程)
2014/03/14 Python
Python标准库之多进程(multiprocessing包)介绍
2014/11/25 Python
在Gnumeric下使用Python脚本操作表格的教程
2015/04/14 Python
利用python打印出菱形、三角形以及矩形的方法实例
2017/08/08 Python
基于numpy.random.randn()与rand()的区别详解
2018/04/17 Python
python 使用装饰器并记录log的示例代码
2019/07/12 Python
Python基于BeautifulSoup和requests实现的爬虫功能示例
2019/08/02 Python
荷兰皇家航空公司中国官网:KLM中国
2017/12/13 全球购物
酒吧员工的岗位职责
2013/11/26 职场文书
专科毕业生自我鉴定
2013/12/01 职场文书
安全资金保障制度
2014/01/23 职场文书
医学生临床实习自我评价
2014/03/07 职场文书
鸿星尔克广告词
2014/03/21 职场文书
新教师培训心得体会
2014/09/02 职场文书
个人对照检查材料思想汇报(四风问题)
2014/09/25 职场文书
2014年党风廉政工作总结
2014/12/03 职场文书
被告代理词范文
2015/05/25 职场文书
党员转正介绍人意见
2015/06/03 职场文书
担保书范文
2019/07/09 职场文书
vue完美实现el-table列宽自适应
2021/05/08 Vue.js
Lakehouse数据湖并发控制陷阱分析
2022/03/31 Oracle