详解在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 相关文章推荐
Javascript miscellanea -display data real time, using window.status
Jan 09 Javascript
Javascript中的高阶函数介绍
Mar 15 Javascript
浅谈jquery事件处理
Apr 24 Javascript
Jquery组件easyUi实现选项卡切换示例
Aug 23 Javascript
实例浅析js的this
Dec 11 Javascript
Javascript Function.prototype.bind详细分析
Dec 29 Javascript
原生js实现新闻列表展开/收起全文功能
Jan 20 Javascript
jQuery实现ajax无刷新分页页码控件
Feb 28 Javascript
js指定步长实现单方向匀速运动
Jul 17 Javascript
基于jquery.page.js实现分页效果
Jan 01 jQuery
SVG实现时钟效果
Jul 17 Javascript
微信小程序访问豆瓣电影api的实现方法
Mar 31 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
table标签的结构与合并单元格的实现方法
2013/07/24 PHP
PHP实现的QQ空间g_tk加密算法
2015/07/09 PHP
PHP和C#可共用的可逆加密算法详解
2015/10/26 PHP
解读PHP的Yii框架中请求与响应的处理流程
2016/03/17 PHP
php常用数组函数实例小结
2016/12/29 PHP
实现JavaScript中继承的三种方式
2009/10/16 Javascript
jquery 问答知识整理
2010/02/11 Javascript
js 内存释放问题
2010/04/25 Javascript
验证控件与Button的OnClientClick事件详细解析
2013/12/04 Javascript
JavaScript模拟实现键盘打字效果
2015/06/29 Javascript
JS模仿编辑器实时改变文本框宽度和高度大小的方法
2015/08/17 Javascript
对jQuary选择器的全面总结
2016/06/20 Javascript
js严格模式总结(分享)
2016/08/22 Javascript
javascript实现简单的on事件绑定
2016/08/23 Javascript
微信小程序开发探究
2016/12/27 Javascript
Vue实现选择城市功能
2017/05/27 Javascript
用js实现每隔一秒刷新时间的实例(含年月日时分秒)
2017/10/25 Javascript
vuex实现像调用模板方法一样调用Mutations方法
2019/11/06 Javascript
python益智游戏计算汉诺塔问题示例
2014/03/05 Python
python 网络编程常用代码段
2016/08/28 Python
django 通过ajax完成邮箱用户注册、激活账号的方法
2018/04/17 Python
Python OpenCV调用摄像头检测人脸并截图
2020/08/20 Python
使用python-opencv读取视频,计算视频总帧数及FPS的实现
2019/12/10 Python
加拿大最大的箱包及旅游配件零售商:Bentley Leathers
2017/07/19 全球购物
阿迪达斯荷兰官方网站:adidas荷兰
2018/03/16 全球购物
Ruby如何实现动态方法调用
2012/11/18 面试题
初任培训自我鉴定
2013/10/07 职场文书
财务总经理岗位职责
2014/02/16 职场文书
绿色出行口号
2014/06/18 职场文书
2014市府办领导班子“四风问题”对照检查材料思想汇报
2014/09/24 职场文书
处级干部考察材料
2014/12/24 职场文书
英文商务邀请函范文
2015/01/31 职场文书
运动会新闻稿
2015/07/17 职场文书
房地产置业顾问工作总结
2015/10/23 职场文书
Python-typing: 类型标注与支持 Any类型详解
2021/05/10 Python
Java对文件的读写操作方法
2022/04/29 Java/Android