详解在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 Discuz代码中的msn聊天小功能
May 25 Javascript
使用非html5实现js板连连看游戏示例代码
Sep 22 Javascript
Ext GridPanel加载完数据后进行操作示例代码
Jun 17 Javascript
javascript显式类型转换实例分析
Apr 25 Javascript
浅谈JavaScript变量的自动转换和语句
Jun 12 Javascript
浅谈angularJS中的事件
Jul 12 Javascript
javascript中Date对象的使用总结
Nov 21 Javascript
jQueryUI 拖放排序遇到滚动条时有可能无法执行排序的小bug及解决方案
Dec 19 Javascript
jQuery实现元素的插入
Feb 27 Javascript
Vue制作Todo List网页
Apr 26 Javascript
Vue中render方法的使用详解
Jan 26 Javascript
Vue3.0结合bootstrap创建多页面应用
May 28 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
WIN98下Apache1.3.14+PHP4.0.4的安装
2006/10/09 PHP
让PHP支持页面回退的两种方法
2008/01/10 PHP
php利用单例模式实现日志处理类库
2014/02/10 PHP
php简单统计中文个数的方法
2016/09/30 PHP
PHP Swoole异步Redis客户端实现方法示例
2019/10/24 PHP
超级兔子让浮动层消失的前因后果
2007/03/09 Javascript
jQuery在vs2008及js文件中的无智能提示的解决方法
2010/12/30 Javascript
基于jQuery的公告无限循环滚动实现代码
2012/05/11 Javascript
使用javascript控制cookie显示和隐藏背景图
2014/02/12 Javascript
JavaScript数据结构和算法之图和图算法
2015/02/11 Javascript
Java File类的常用方法总结
2015/03/18 Javascript
jquery图片轮播特效代码分享
2020/04/20 Javascript
Angular2 (RC4) 路由与导航详解
2016/09/21 Javascript
实例解析angularjs的filter过滤器
2016/12/14 Javascript
JavaScript错误处理和堆栈追踪详解
2017/04/18 Javascript
vue 详情跳转至列表页实现列表页缓存
2019/03/27 Javascript
react实现同页面三级跳转路由布局
2019/09/26 Javascript
vue实现整屏滚动切换
2020/06/29 Javascript
对vue生命周期的深入理解
2020/12/03 Vue.js
[00:12]2018DOTA2亚洲邀请赛SOLO赛 MidOne是否中单第一人?
2018/04/05 DOTA
[01:02:06]LGD vs Mineski Supermajor 胜者组 BO3 第二场 6.5
2018/06/06 DOTA
python生成随机mac地址的方法
2015/03/16 Python
Python部署web开发程序的几种方法
2017/05/05 Python
Python3解决棋盘覆盖问题的方法示例
2017/12/07 Python
Python程序员面试题 你必须提前准备!(答案及解析)
2018/01/23 Python
python 删除指定时间间隔之前的文件实例
2018/04/24 Python
详解python爬虫系列之初识爬虫
2019/04/06 Python
python使用原始套接字发送二层包(链路层帧)的方法
2019/07/22 Python
wxPython实现带颜色的进度条
2019/11/19 Python
python复合条件下的字典排序
2020/12/18 Python
纯CSS实现聊天框小尖角、气泡效果
2014/04/04 HTML / CSS
微软英国官方网站:Microsoft英国
2016/10/15 全球购物
Paul Smith英国官网:英国国宝级时装品牌
2019/03/21 全球购物
关于教师节的广播稿
2014/09/10 职场文书
单位委托书范本(3篇)
2014/09/18 职场文书
Nginx反向代理学习实例教程
2021/10/24 Servers