当前位置: 首页 > news >正文

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

先来几个术语:

官方我的说法对应代码
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>])     
}

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

这次就科普到这里吧。

相关文章:

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

先来几个术语&#xff1a; 官方我的说法对应代码React elementReact元素let element<span>A爆了</span>Component组件class App extends React.Component {}无App为父元素&#xff0c;App1为子元素<App><App1></App1></App> 本文重点&…...

C++17 nodiscard标记符

文章目录前言弃值表达式nodiscard标记符函数非弃值声明类/枚举类/结构 非弃值声明返回类引用与类指针前言 在C 17中引入了一个标记符nodiscard&#xff0c;用于声明一个 “非弃值(no-discard)表达式”。那么在开始之前&#xff0c;我们需要了解一下什么是弃值表达式。 弃值表…...

SAP 寄售业务的标准流程

SAP的标准寄售业务&#xff0c;供应商提供的物料只有在公司使用之后才需支付应付账款&#xff0c;类似是一种先吃后付钱的餐饮流程。 SAP的寄售流程把实际业务中的供应商&#xff0c;采购方收货&#xff0c;采购方消耗物料&#xff0c;采购方依据消耗物料数量进行付款&#xff…...

操作系统高频知识

目录 一、线程与进程的区别 区别&#xff1a; 二、多进程和多线程区别 三、进程与程序的区别 三、死锁 1、是什么 2、产生的原因 3、产生的必要条件&#xff08;4个&#xff09; 4、如何预防 5、如何避免 6、如何检测 7、如何解除 一、线程与进程的区别 1、线程&a…...

加载预训练模型,模型微调,在自己的数据集上快速出效果

针对于某个任务&#xff0c;自己的训练数据不多&#xff0c;先找到一个同类的别人训练好的模型&#xff0c;把别人现成的训练好了的模型拿过来&#xff0c;换成自己的数据&#xff0c;调整一下参数&#xff0c;再训练一遍&#xff0c;这就是微调&#xff08;fine-tune&#xff…...

VScode远程连接服务器-过程试图写入的管道不存在-could not establist connection to【已解决】

问题描述 使用服务器的过程中突然与服务器断连&#xff0c;报错如下&#xff1a;could not establist connection to [20:23:39.487] > ssh: connect to host 10.201.0.131 port 22: Connection timed out > [20:23:39.495] > 过程试图写入的管道不存在。 > [20…...

电子技术——B类输出阶

电子技术——B类输出阶 下图展示了一个B类输出阶的原理图&#xff0c;B类输出阶由两个互补的BJT组成&#xff0c;不同时导通。 原理 当输入电压 vI0v_I 0vI​0 的时候&#xff0c;两个晶体管都截止输出电压为零。当 vIv_IvI​ 上升至超过0.5V的时候&#xff0c;此时 QNQ_NQN…...

【老卫搬砖】034期:HarmonyOS 3.1 Beta 1初体验,我在本地模拟器里面刷短视频

今天啊打开这个DevEco Studio的话&#xff0c;已经提示有3.1Beta1版本的一个更新啊。然后看一下它的一些特性。本文也演示了如何在本地模拟器里面运行HarmonyOS版短视频。 主要特性 新特性包括&#xff1a; Added support for Windows 11 64-bit and macOS 13.x OSs, as well…...

Day901.内部临时表 -MySQL实战

内部临时表 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于内部临时表的内容。 sort buffer、内存临时表和 join buffer。这三个数据结构都是用来存放语句执行过程中的中间数据&#xff0c;以辅助 SQL 语句的执行的。 其中&#xff0c;在排序的时候用到了 sort bu…...

jstatd的启动方式与关闭方式

启动方式与注意事项&#xff1a; 启动方式&#xff1a; 前台启动不打印日志&#xff1a; jstatd -J-Djava.security.policyjstatd.all.policy -J-Djava.rmi.server.hostname服务器IP 前台启动并打印日志&#xff1a; ./jstatd -J-Djava.security.policyjstatd.all.policy -…...

_improve-3

createElement过程 React.createElement()&#xff1a; 根据指定的第一个参数创建一个React元素 React.createElement(type,[props],[...children] )第一个参数是必填&#xff0c;传入的是似HTML标签名称&#xff0c;eg: ul, li第二个参数是选填&#xff0c;表示的是属性&#…...

C++——异常

目录 C语言传统的处理错误的方式 C异常概念 异常的使用 异常的抛出和匹配原则 在函数调用链中异常栈展开匹配原则 自定义异常体系 异常的重新抛出 ​编辑 异常安全 异常规范 C标准库的异常体系 异常的优缺点 C语言传统的处理错误的方式 传统的错误处理机制&#xff1a; …...

MVVM 架构进阶:MVI 架构详解

前言Android开发发展到今天已经相当成熟了&#xff0c;各种架构大家也都耳熟能详&#xff0c;如MVC,MVP,MVVM等&#xff0c;其中MVVM更是被官方推荐&#xff0c;成为Android开发中的显学。不过软件开发中没有银弹&#xff0c;MVVM架构也不是尽善尽美的&#xff0c;在使用过程中…...

有没有必要考PMP证书?

其实针对有没有必要考试吗&#xff0c;这个可以根本不同行业的人来决定的。 1.高等教育项目管理专业科班出身的人员。 在我国本科学历和硕士研究生学历中&#xff0c;项目管理也有开设。不管以后从事的工作是否为项目管理或其他管理&#xff0c;作为本专业的同学&#xff0c;…...

1 机器学习基础

1 机器学习概述 1.1 数据驱动的问题求解 大数据-Big Data 大数据的多面性 1.2 数据分析 机器学习&#xff1a;海量的数据&#xff0c;获取有用的信息 专门研究计算机怎样模拟或实现人类的学习行为&#xff0c;以获取新的知识或技能&#xff0c;重新组织已有的知识结构使之…...

java基础系列(六) sleep()和wait() 区别

一.前言 关于并发编程这块, 线程的一些基础知识我们得搞明白, 本篇文章来说一下这两个方法的区别,对Android中的HandlerThread机制原理可以有更深的理解, HandlerThread源码理解,请查看笔者的这篇博客: HandlerThread源码理解_handlerthread 源码_broadview_java的博客-CSDN博…...

Urho3D序列化

从Serializable派生的类可以通过定义属性将其自动序列化为二进制或XML格式。属性存储到每个类的上下文中。场景加载/保存和网络复制都是通过从Serializable派生Node和Component类来实现的。 支持的属性类型是Variant支持的所有属性类型&#xff0c;不包括指针和自定义值。 属性…...

企业级信息系统开发学习1.3——利用注解配置取代Spring配置文件

文章目录一、利用注解配置类取代Spring配置文件&#xff08;一&#xff09;打开项目&#xff08;二&#xff09;创建新包&#xff08;三&#xff09;拷贝类与接口&#xff08;四&#xff09;创建注解配置类&#xff08;五&#xff09;创建测试类&#xff08;六&#xff09;运行…...

VUE DIFF算法之快速DIFF

VUE DIFF算法系列讲解 VUE 简单DIFF算法 VUE 双端DIFF算法 文章目录VUE DIFF算法系列讲解前言一、快速DIFF的代码实现二、实践练习1练习2总结前言 本节我们来写一下VUE3中新的DIFF算法-快速DIFF&#xff0c;顾名思义&#xff0c;也就是目前最快的DIFF算法&#xff08;在VUE中&…...

一文掌握如何轻松稿定项目风险管理【静说】

风险管理对于每个项目经理和PMO都非常重要&#xff0c;如果管理不当会出现很多问题&#xff0c;咱们以前分享过很多风险管理的内容&#xff1a; 风险无处不在&#xff0c;一旦发生&#xff0c;会对一个或多个项目目标产生积极或消极影响的确定事件或条件。那么接下来介绍下五大…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

js 设置3秒后执行

如何在JavaScript中延迟3秒执行操作 在JavaScript中&#xff0c;要设置一个操作在指定延迟后&#xff08;例如3秒&#xff09;执行&#xff0c;可以使用 setTimeout 函数。setTimeout 是JavaScript的核心计时器方法&#xff0c;它接受两个参数&#xff1a; 要执行的函数&…...