React组件之间的通信方式总结(上)

news2024/11/17 3:27:22

先来几个术语:

官方我的说法对应代码
React elementReact元素let element=<span>A爆了</span>
Component组件class App extends React.Component {}
App为父元素,App1为子元素<App><App1></App1></App>

本文重点:

  • 组件有两个特性
    • 1、传入了一个“props”
    • 2、返回了一个React元素
  • 组件的构造函数
    • 如果需要重新定义constructor,必须super一下,才能激活this,也就是可以用来自React.component方法
  • 组件的props
    • 是可读的,也就是不能在组件中修改prop的属性
    • JSX中传入对象的props,可以通过{…object}的方式
  • 父子元素之间的通信(初级版本)
    • 父=>子,通过父元素的render既可改变子元素的内容。
    • 子=>夫,通过父元素传入子元素中的props上挂载的方法,让子元素触发父元素中的方法,从而进行通信。

Component

上回说到JSX的用法,这回要开讲react组件之间的一个沟通。那么什么是组件?我知道英文是Component,但这对我而言就是一个单词,毫无意义。要了解Component之间是如何进行友好交流的,那就要先了解Component是个什么鬼。

上回说到的JSX,我们可以这么创建对象:

let element=<h1 className="aaa">A爆了</h1>
//等同于
let element=React.createElement(
  "h1",
  {className:"aaa"},
  "A爆了"
)

还是老老实实地用h1div这种标准的HTML标签元素去生成React元素。但是这样的话,我们的JS就会变得巨大无比,全部都是新建的React元素,有可能到时候我们连对象名都不晓得怎么起了,也许就变成let div1;let div2这样的。哈哈哈开个玩笑。但是分离是肯定要分离的。这个时候就有了名为Component的概念。他可以做些什么呢?简单的说就是创建一个个独立的可复用的小组件。话不多说,我们来瞅瞅来自官方的写法:

写法一:函数型创建组件,大家可以看到我就直接定义一个名为App的方法,每次执行App()的时候就会返回一个新的React元素。而这个方法我们可以称之为组件Component。有些已经上手React的朋友,可能傻了了,这是什么操作,我的高大上class呢?extend呢?很遗憾地告诉你,这也是组件,因为他符合官方定义:1、传入了一个“props” ,2、返回了一个React元素。满足上述两个条件就是Component!

function App(props) {
  return <span>{props.name}A爆了</span>     
}

这个是最简易的Component了,在我看来Component本身是对React.createElement的一种封装,他的render方法就相当于React.createElement的功能。高大上的组件功能来啦:

import React, { Component } from 'react';
class App extends Component {
  render() {
    return <span>{this.props.name}!A爆了</span>     
  }
}
export default App;

参考 前端进阶面试题详细解答

这个class版本的组件和上方纯方法的组件,从React的角度上来说,并无不同,但是!毕竟我class的方式还继承了React.Component,不多点小功能都说不过去对吧?所以说我们这么想继承了React.Component的组件的初始功能要比纯方法return的要多。所以每个React的Component我们都可以当作React元素直接使用。

好了,我们来研究研究Component这个类的方法吧。

首先是一个神奇的constructor函数,这个函数在类中,可以说是用于初始化的函数。如果省去不写,也不会出错,因为我们的组件都是React.Component的子类,所以都继承了React.Componentconstructor方法。如果我们在子类Component中定义了constructor相当于是覆盖了父类的方法,这样React.Component的构造函数就失效了。简单地来说就是很多默认的赋值都失效了。你是获取不到props的。因此官方为了提醒大家不要忘记super一下,也就是继承父类的constructor,因此会报"this hasn't been initialised - super() hasn't been called"这个错误。意思就是你先继承一下。也就是说super是执行了父类的constructor的方法。所以!!!重点来了——我们写super的时候不能忘记传入props。不传入props,程序就无法获取定义的组件属性了。

constructor(props) {
    super(props);//相当于React.Component.call(this,props)
}

官方也给大家划重点了:

Class components should always call the base constructor with props.(类组建在执行基本constructor的时候,必须和props一起。)

对于我们没有写constructor,但在其他自带方法中,比如render,也可以直接获取到props,这个诡异的操作就可以解释了。因为我们省略了重定义,但是constructor本身不仅是存在的而且也执行了,只不过没有在我们写的子类中体现出来而已。

props的坑

分析了Component之后,大家有没有发现Component的一个局限?没错!就是传参!关于Component的一个定义就是,只能传入props的参数。也就是说所有的沟通都要在这个props中进行。有种探监的既视感,只能在规定的窗口,拿着对讲机聊天,其他的方式无法沟通。React对于props有着苛刻的规定。

All React components must act like pure functions with respect to their props.

简单地来说就是props是不能被改变的,是只读的。(大家如果不信邪,要试试,可以直接改props的值,最终等待你的一定是报错页面。)

这里需要科普下纯函数pure function的概念,之后Redux也会遇到的。意思就是纯函数只是一个过程,期间不改变任何对象的值。因为JS的对象有个很奇怪的现象。如果你传入一个对象到这个方法中,并且改变了他某属性的值,那么传入的这个对象在函数外也会改变。pure function就是你的改动不能对函数作用域外的对象产生影响。所以每次我们在Component里面会遇到一个新的对象state,一般这个组件的数据我们会通过state在当前组件中进行变化处理。

划重点:因为JS的特性,所以props设置为只读,是为了不污染全局的作用域。这样很大程度上保证了Component的独立性。相当于一个Component就是一个小世界。

我发现定义props的值也是一门学问,也挺容易踩坑的。

比如下方代码,我认为打印出来应该是props:{firstName:"Nana",lastName:"Sun"...},结果是props:{globalData:true}.

let globalData={
    firstName:"Nana",
    lastName:"Sun",
    greeting:["Good moring","Good afternoon","Good night"]
}
ReactDOM.render(<App globalData/>, document.getElementById('root'));

所以对于props是如何传入组件的,我觉得有必要研究一下下。

props其实就是一个参数直接传入组件之中的,并未做什么特殊处理。所以对props进行处理的是在React.createElement这一个步骤之中。我们来回顾下React.createElement是怎么操作的。

React.createElement(
  "yourTagName",
  {className:"aaa",color:"red:},  "文字/子节点")//对应的JSX写法是:<yourTagName className="aaa" color="red>文字/子节点</yourTagName>

也就是他的语法是一个属性名=属性值,如果我们直接放一个<App globalData/>,那么就会被解析成<App globalData=true/>},所以props当然得不到我们想要的结果。这个是他的一个语法,我们无法扭转,但是我们可以换一种写法,让他无法解析成属性名=属性值,这个写法就是{...globalData},解构然后重构,这样就可以啦。

Components之间的消息传递

单个组件的更新->setState

Components之间的消息传递是一个互动的过程,也就是说Component是“动态”的而不是“静态”的。所以首先我们得让静态的Component“动起来”,也就是更新组件的的值,前面不是提过props不能改嘛,那怎么改?前文提过Component就是一个小世界,所以这个世界有一个状态叫做state

先考虑如何外力改变Component的状态,就比如点击啦,划过啦。

class App extends Component {
  state={
      num:0
  }
  addNum=()=>{
    this.setState({
      num:this.state.num+1
    })
  }
  render() {
    return( [
        <p>{this.state.num}</p>,
        <button onClick={this.addNum}>点我+1</button>
      ]
    )     
  }
}

这里我用了onClick的用户主动操作的方式,迫使组件更新了。其实component这个小世界主要就是靠state来更新,但是不会直接this.state.XXX=xxx直接改变值,而是通过this.setState({...})来改变。

这里有一个小tips,我感觉大家很容易犯错的地方,有关箭头函数的this指向问题,大家看下图。箭头函数转化成ES5的话,我们就可以很清晰得看到,箭头函数指向他上一层的函数对象。这里也就指向App这个对象。

如果不想用箭头函数,那么就要注意了,我们可以在onClick中加一个bind(this)来绑定this的指向,就像这样onClick={this.addNum.bind(this)}

  render() {
    return( [
        <p>{this.state.num}</p>,
        <button onClick={this.addNum.bind(this)}>点我+1</button>
      ]
    )     
  }

组件之间的通信

那么Component通过this.setState可以自high了,那么组件之间的呢?Component不可能封闭自己,不和其他的Component合作啊?那我们可以尝试一种方式。

在App中我把<p>{this.state.num}</p>提取出来,放到App1中,然后App1直接用props来显示,因为props是来自父元素的。相当于我直接在App(父元素)中传递num给了App1(子元素)。每次App中state发生变化,那么App1就接收到召唤从而一起更新。那么这个召唤是基于一个什么样的理论呢?这个时候我就要引入React的生命周期life cycle的问题了。

//App
render() {
  return( [
      <App1 num={this.state.num}/>,
      <button onClick={this.addNum}>点我+1</button>
    ]
  )     
}
//App1
render() {
  return( [
      <p>{this.props.num}</p>,
    ]
  )     
}

react的生命周期

看到生命周期life cycle,我就感觉到了生生不息的循环cycle啊!我是要交代在这个圈圈里了吗?react中的生命周期是干嘛的呢?如果只是单纯的渲染就没有生命周期一说了吧,毕竟只要把内容渲染出来,任务就完成了。所以这里的生命周期一定和变化有关,有变化才需要重新渲染,然后再变化,再渲染,这才是一个圈嘛,这才是life cycle。那么React中的元素变化是怎么变的呢?

先来一个官方的生命周期(我看着就头晕):

点我看live版本

官方的全周期:

官方的简约版周期:

有没有看着头疼,反正我是跪了,真令人头大的生命周期啊。我还是通过实战来确认这个更新是怎么产生的吧。实战出真理!(一些不安全的方法,或者一些我们不太用得到的,这里就不讨论了。)

Mounting装备阶段:

  • constructor()
  • render()
  • componentDidMount()

Updating更新阶段:

  • render()
  • componentDidUpdate()
  • 具有争议的componentWillReceiveProps()

Unmounting卸载阶段:

  • componentWillUnmount()

Error Handling错误捕获极端

  • componentDidCatch()

这里我们通过运行代码来确认生命周期,这里是一个父元素嵌套子元素的部分代码,就是告诉大家,我在每个阶段打印了啥。这部分的例子我用的还是上方的App和App1的例子。

//father
constructor(props){
  console.log("father-constructor");
}
componentDidMount() {
  console.log("father-componentDidMount");
}
componentWillUnmount() {
  console.log("father-componentWillUnmount");
}
componentDidUpdate() {
  console.log("father-componentDidUpdate");
}
render() {
  console.log("father-render");
}
//child
constructor(props){
  console.log("child-constructor");
  super(props)
}
componentDidMount() {
  console.log("child-componentDidMount");
}
componentWillUnmount() {
  console.log("child-componentWillUnmount");
}
componentDidUpdate() {
  console.log("child-componentDidUpdate");
}
componentWillReceiveProps(){
  console.log("child-componentWillReceiveProps");
}
render() {
  console.log("child-render");
}

好了开始看图推理

初始化运行状态:

父元素先运行创建这没有什么问题,但是问题是父元素还没有运行结束,杀出了一个子元素。也就是说父元素在render的时候里面碰到了子元素,就先装载子元素,等子元素装载完成后,再告诉父元素我装载完毕,父元素再继续装载直至结束。

我点击了一下,父元素setState,然后更新了子元素的props

同样的先父元素render,遇到子元素就先暂时挂起。子元素这个时候出现了componentWillReceiveProps,也就是说他是先知道了父元素传props过来了,然后再render。因为有时候我们需要在获取到父元素改变的props之后再执行某种操作,所以componentWillReceiveProps很有用,不然子元素就直接render了。突想皮一下,那么我子元素里面没有props那是不是就不会执行componentWillReceiveProps了??就是<App1 num={this.state.num}/>变成<App1/>。我还是太天真了。这个componentWillReceiveProps依然会执行也就是说:

componentWillReceiveProps并不是父元素传入的props发生了改变,而是父元素render了,就会出发子元素的这个方法。

关于卸载,我们来玩一下,把App的方法改成如下方所示,当num等于2的时候,不显示App1。

render() {
  return( 
    <div>
      {this.state.num===2?"":<App1 num={this.state.num}/>}      <button onClick={this.addNum}>点我+1</button>
    </div>
  )     
}

App先render,然后卸载了App1之后,完成了更新componentDidUpdate

那么大家看懂了生命周期了吗??我总结了下:

  • 父元素装载时render了子元素,就先装载子元素,再继续装载父元素。
  • 父元素render的时候,子元素就会触发componentWillReceiveProps,并且跟着render
  • 父元素卸载子元素时,先render,然后卸载了子元素,最后componentDidUpdate

如何子传父亲呢??

通过生命周期,子元素可以很容易的获取到父元素的内容,但是父元素如何获得来自子元素的内容呢?我们不要忘记了他们为一个沟通桥梁props!我们可以在父元素中创建一个方法用于获取子元素的信息,然后绑定到子元素上,然后不就可以获取到了!操作如下所示:

receiveFormChild=(value)=>{
  console.log(value)
}
render() {
  return( 
    <div>
      {this.state.num===2?"":<App1 num={this.state.num} popToFather={this.receiveFormChild}/>}      <button onClick={this.addNum}>点我+1</button>
    </div>
  )     
}

当子元素运行popToFather的时候,消息就可以传给父亲啦!

子元素:

render() {
  return( [
      <p>{this.props.num}</p>,
      <button onClick={()=>this.props.receiveState("来自子元素的慰问")}>子传父</button>
    ]
  )     
}

父元素成功获取来自子元素的慰问!

这次就科普到这里吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/82984.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

idea中用SSH工具手动打包部署

我用的是MobaXterm工具&#xff1a; 第一步&#xff1a;先将自己的文件整体运行一下&#xff0c;确保可以正常运行 第二步&#xff1a;在Maven中找到名为XXXX&#xff08;root&#xff09;的名称展开 ---》Lifecycle &#xff0c;先点击 clean&#xff0c;确保无误后点击 pac…

clickhouse三节点三分片双副本环绕部署

文章目录clickhouse安装部署01节点 metrika.xml 配置文件信息02节点 metrika.xml 配置文件信息03节点 metrika.xml 配置文件信息macros 实例信息编辑查看结果clickhouse安装部署 rpm安装过程以及注意事项 01节点 metrika.xml 配置文件信息 <?xml version"1.0"…

笔试强训(四十三)

目录一、选择题二、编程题2.1 电话号码2.1.1 题目2.1.2 题解2.2 求和2.2.1 题目2.2.2 题解一、选择题 &#xff08;1&#xff09;下列关于synflood攻击的说法错误的是&#xff08;B&#xff09; A.服务端由于连接队列被占满而不能对外服务 B.不但能攻击TCP服务&#xff0c;还能…

做个家务,让我搞懂了 Linux I/O 模型

I/O 其实就是 input 和 output&#xff08;输入输出&#xff09; 在计算机操作系统中对应数据流的输入与输出&#xff0c;在 Linux 中&#xff0c;既有文件的 I/O&#xff0c;也有网络 I/O 无论是文件 I/O 还是网络 I/O&#xff0c;其传输过程都是类似的 今天我们以文件 I/O…

【Unity学习笔记】UnrealToUnity教程:(网上购买的素材导入Unreal+插件转Unity)

【Unity学习笔记】UnrealToUnity教程&#xff1a; 最近想从Unreal那边化点缘借借素材&#xff0c;没想到踩到一个大坑 一&#xff0c;素材导入Unreal 这个教程比较多&#xff0c;根据素材的来源&#xff0c;传送门是以下这几个&#xff1a; 1.项目之间互相迁移&#xff0c;不…

PGL 系列(一)图的基础概念

一、图知识 图的节点间是否有方向,可将图分为无向图与有向图; 图的边是否有权重,可以将图分为无权图和有权图; 图的边和点是否具有多种类型,可以将图分为同构图和异构图 度是图上一节点,其边的条数 邻居指的是图上一节点的相邻节点 无向图:临界矩阵就是

值得一看,阿里又杀疯了开源内部“M9”级别全彩版分布式实战笔记

学习是一种基础性的能力。然而&#xff0c;“吾生也有涯&#xff0c;而知也无涯。”&#xff0c;如果学习不注意方法&#xff0c;则会“以有涯随无涯&#xff0c;殆矣”。 学习就像吃饭睡觉一样&#xff0c;是人的一种本能&#xff0c;人人都有学习的能力。我们在刚出生的时候…

汽车以太网物理层IOP自动化测试解决方案

解决方案功能介绍 该解决方案的测试目的是定义一组测试&#xff0c;以确保使用具有100 BASE-T1或1000BASE-T1能力的PHYs的多个设备之间的互操作性&#xff0c;要求每个PHY能够在给定的时间限制内建立稳定的链路&#xff0c;能够可靠地监视当前链路状态并将其传递到上层&#x…

CRMEB电商商城系统阿里云ECS服务器安装配置搭建教程文档

阿里云ECS服务器配置教程&#xff1a;一、推荐使用宝塔Linux面板&#xff0c;简单好用。二、放行服务器端口。详细步骤&#xff1a; 1.在阿里云控制台&#xff0c;云服务器ECS&#xff0c;实例&#xff0c;点击最右侧更多→实力状态→停止。 2.选择停止&#xff0c;点击确定. …

Java培训堆 Heap永久区

永久区 永久存储区是一个常驻内存区域&#xff0c;用于存放JDK自身所携带的 Class,Interface 的元数据&#xff0c;也就是说它存储的是运行环境必须的类信息&#xff0c;被装载进此区域的数据是不会被垃圾回收器回收掉的&#xff0c;关闭 JVM 才会释放此区域所占用的内存。 J…

Cross-modal Pretraining in BERT(跨模态预训练)

BERT以及BERT后时代在NLP各项任务上都是强势刷榜&#xff0c;多模态领域也不遑多让…仅在2019 年就有8篇的跨模态预训练的论文挂到了arxiv上…上图是多篇跨模态论文中比较稍迟的VL-BERT论文中的比较图&#xff0c;就按这个表格的分类&#xff08;Architecture&#xff09;整理这…

[LeetCode 1781]所有子字符串美丽值之和

题目描述 题目链接&#xff1a;所有子字符串美丽值之和 一个字符串的 美丽值 定义为&#xff1a;出现频率最高字符与出现频率最低字符的出现次数之差。 比方说&#xff0c;“abaacc” 的美丽值为 3 - 1 2 。 给你一个字符串 s &#xff0c;请你返回它所有子字符串的 美丽值…

微信小程序框架(五)-全面详解(学习总结---从入门到深化)

目录 UI框架_TDesign 引入TDesign UI框架_TDesign组件 Rate 评分 Toast 轻提示 UI框架_Vant 使用方式 引入组件 UI框架_Vant组件 Overlay 遮罩层 Grid 宫格 Card 商品卡片 UI框架_TDesign TDesign 企业级设计体系&#xff08;前端UI组件库&#xff09; 地址&#xff1a;…

影响项目进度的因素有哪些?如何跟踪项目计划?

影响项目进度的因素&#xff1a; 1、项目经理能力不足 一名优秀的项目经理&#xff0c;要对制定项目计划、安全、质量、成本管理等各项工作都熟悉&#xff0c;也要能管理好项目团队。 如果无法有效管理团队、没有完全掌握项目管理方法等都是项目经理能力不足的表现&#xff…

跳槽一次能涨多少?今天见识到跳槽天花板。

2022年马上就快结束了&#xff0c;最近内卷严重&#xff0c;各种跳槽裁员&#xff0c;相信很多小伙伴也在准备明年的金三银四的面试计划。 在此分享一套学习笔记 / 面试手册&#xff0c;年后跳槽的朋友可以好好刷一刷&#xff0c;还是挺有必要的&#xff0c;它几乎涵盖了所有的…

软件测试必须要加班吗?

我在面试时一般会问面试官&#xff1a;“公司每次迭代周期多久&#xff1f;加班多么&#xff1f;”这个问题一般都是看这家公司的上班节奏怎么样&#xff0c;加班多不多&#xff0c;自己能不能扛住加班的压力。一般来说&#xff0c;刚进公司&#xff0c;需要对环境、业务、代码…

10:30面试,10:31就出来了 ,问的实在是太...

从外包出来&#xff0c;没想到算法死在另一家厂子 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以也就忍了。没想到8月一纸通知&#xff0c;所有人不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个兄弟内推…

CMake中target_include_directories的使用

CMake中target_include_directories命令用于向target中添加包含目录,其格式如下: target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]<INTERFACE|PUBLIC|PRIVATE> [items1...][<INTERFACE|PUBLIC|PRIVATE> [items2...] ...]) 指定在编译给定targ…

MySQL 事务特性和事务隔离级别

1. MySQL 事务的四大特性 2. MySQL 事务的并发问题 3. MySQL 事务的隔离级别 --------------------------------------------------------- 1. MySQL 事务的四大特性 MySQL 事务具有四个特性&#xff1a;原子性、一致性、隔离性、持久性&#xff0c;这四个特性简称 ACID 特性…

写文章常用的几款软件

编写文章Typora Typora网址 就是一款非常优秀的桌面端笔记本软件, 支持实时预览的 Markdown 文本编辑器. 支持导出HTML, PDF, Word等格式 画图excalidraw excalidraw在线网址 一个开源免费的画图软件 目前看到的最舒服的画图软件, 拥有丰富的图库, 好看的风格 录制GIF的LICEc…