前言
1990年,伊拉克违反联合国宪章,武装占领了科威特。此举引起了国际社会的强烈谴责,联合国安理会先后通过了十多个决议,要求伊拉克从科威特撤军,但是均被伊拉克无视。萨达姆的一意孤行让联合国明白,不得不采取“一切必要手段”来执行先前通过的各项决议。1991年,美国正式宣布向伊拉克开战,颠覆战争概念的“海湾战争”就此展开。
萨达姆坐拥百万大军,坦克、飞机、导弹数不胜数,麾下军队又大都经历过两伊战争,作战经验丰富。这些军事力量是他屡次无视联合国的资本。面对美军的开战宣言,面对多国军队的集结,他心里想的是:“放马过来吧。”
开战前,大家认为这将是一场激烈、持久的战争,是对联合国部队和平行动的巨大挑战和作战能力的巨大考验。然而,美国以迅雷之势,让全球明白了何谓现代战争,何谓信息化战争。
它先是以电子战飞机为先锋,重创伊拉克的雷达,“瞎其双眼”;再派出新型战斗机、“隐形”轰炸机,破坏伊拉克的防空武装,“断其双臂”;紧跟着便是对伊拉克地面指挥中心、军事基地的地毯式轰炸,“坏其大脑”。
萨达姆的军事力量在美军的组合拳面前毫无抵抗能力。本以为是两位旗鼓相当的拳击手同台竞技,“正义”方众望所归地艰难战胜“邪恶”方的故事,事实上却是一场重量级拳击手对着一个大一点的沙包练习拳击技巧的公开课。
美国在这场战争中表现出色的背后,有一支高度协同工作的军事信息通讯团队。在战场前线的士兵需要实时获取情报和指示,指挥官需要即刻传达命令,而后勤人员需要确保物资的准确分配。怎么做到,如何兼顾? 70多颗卫星提供侦察、通信的保障,先进的通讯技术,将战场上的各个部队、各个指挥官之间紧密连接起来。无论是在战场的最前线,还是在后方指挥中心,军队可以几乎实时地交换情报、命令和指令,使整个联合行动变得更加精确和高效。
正如在战场上,不同的军队和指挥官需要高效地沟通协作,现代软件开发中的组件也需要相互通信和协作,以构建复杂的应用程序,满足用户的多样化需求。
React的组件间通信方式有哪些?又是如何实现的?就让我们一起在本文中探索吧~
概念先行
从第一篇开始,此类文章的大方向便是留心之前在技术道路上匆忙前行,而错过的风景。于是乎,在我们开始讨论React组件通信之前,不妨先问问自己,什么是React组件?组件通信中的“信”是什么?
组件
组件是构成用户界面(UI)的基础。React 允许你将标签、CSS 和 JavaScript 组合成自定义“组件”,即 应用程序中可复用的 UI 元素。React 组件是一段可以 使用标签进行扩展 的 JavaScript 函数。 ——React 官方文档
我们访问掘金官网,映入眼帘的是经典的三栏布局,上方是导航栏,左侧是菜单栏,中间是内容,右侧是列表,它们都是“组件”,它们一起组成了这张页面。
这张页面当然也不仅仅只具备展示的作用,它还向用户提供了许多交互行为的入口,比如:我们点击上方导航栏内的不同标签,页面中间那一栏就会呈现与之对应的内容。
它们明明是两个不同的组件,如何做到点击A组件中特定的标签,使得B组件展示对应的内容呢?换句话说,B组件它是怎么知道A组件中发生的变化,然后根据这个变化去做出对应的行为呢?
在现实生活中,你邀请你的老同学一起吃饭,你提前到了餐厅准备点菜,但是时隔多年,你忘记了老同学的喜好和忌口,你会怎么做呢?
那还不简单,打个电话问问呗,打电话不好意思的话,那就发个微信问问呗,于是你就完成了与你老同学的一次通信。
通过打电话的方式,你从老同学那得到了你想要的信息,这些信息帮助你完成了点菜这一行为。
组件也是如此,当我们期望两个组件产生耦合时,我们需要先为这两个组件建立连接(打电话),实现组件间通信。
组件通信中的“信”
老同学口味喜好的信息通过电话通讯的方式传递到了相隔几个区的我这里,我们之间由此产生了信息上的连接。组件间通信亦同,它们之间建立的是数据上的连接。
是数据的改变,使得页面内容产生了改变。点击组件A的这一交互行为改变了某个数据,使得组件B获得该数据后,根据数据的变化而变化,从而展示了对应的页面内容。这其实就是React的核心特征————数据驱动视图的一种体现。
数据驱动视图
UI = f(data)
React的视图会随着数据的变化而变化。
单向数据流
打电话之前要输入正确的电话号码,避免打错的情况发生。那么在React中的数据流动,是否也同样遵循一种规则,从而保证数据能够正确地传递、被使用呢? 是的,这一规则便是单向数据流。
当前组件的state以props形式流动时,只能流向组件树中比自己层级更低的组件。 ———@修言
小结一下,要构建一张具备多种功能包含多种信息的网页,离不开多个组件之间的通信和互相协作。数据的变化带来视图上的变化,其在组件间的流动遵循“单向数据流”的规则。
组件通信的方式
父子通信
父组件中的state可以通过props的方式直接传入子组件
graph TD
父组件 --props--> 子组件
代码
import { useState } from "react";
import { Card, Space, Descriptions, Button } from "antd";
import { ChildComponent } from "./child-component";
export function ParentComponent() {
const [text, setText] = useState("初始化的文字");
const changeText = () => {
setText("更改后的文字");
};
const reset = () => {
setText("初始化的文字");
};
return (
<Card
title="父组件"
style={{ width: 600, height: 600 }}
extra={
<Space>
<Button type="primary" onClick={changeText}>
更改
</Button>
<Button onClick={reset}>复原</Button>
</Space>
}
>
<Space style={{ display: "flex", width: "100%" }} direction="vertical">
<Descriptions>
<Descriptions.Item label="父组件文本">{text}</Descriptions.Item>
</Descriptions>
<ChildComponent text={text} />
</Space>
</Card>
);
}
import { Card } from "antd";
export type ChildComponentProps = {
text: string;
};
export function ChildComponent({ text }: ChildComponentProps) {
return (
<Card title="子组件" style={{ width: 300, height: 300 }}>
{text}
</Card>
);
}
子父通信
graph TD
父组件 --yes--> 子组件
子组件 --no--> 父组件
由于单向的限制,子组件显然不能将自己的state直接地自下向上传递给父组件。
但是我们又想到,React是对数据流的传递方向做了严格的限制,而并没有对父组件通过props形式所传递的state类型进行严格限制。
我们可以让父组件向子组件传递一个函数,以函数入参的形式从子组件那拿到数据。
- 这里如果是类组件,那么这个父组件传递的函数需要绑定自身的上下文,即一定是一个写在父组件内部的函数
import { useState } from "react";
import { Card, Space, Descriptions, Button } from "antd";
import { ChildComponent } from "./child-component";
export function ParentComponent() {
const [text, setText] = useState("初始化的文字");
const changeText = () => {
setText("更改后的文字");
};
const reset = () => {
setText("初始化的文字");
};
return (
<Card
title="父组件"
style={{ width: 600, height: 600 }}
extra={
<Space>
<Button type="primary" onClick={changeText}>
更改
</Button>
<Button onClick={reset}>复原</Button>
</Space>
}
>
<Space style={{ display: "flex", width: "100%" }} direction="vertical">
<Descriptions>
<Descriptions.Item label="父组件文本">{text}</Descriptions.Item>
</Descriptions>
<ChildComponent text={text} setParentText={setText} />
</Space>
</Card>
);
}
import { Card, Button } from "antd";
export type ChildComponentProps = {
text: string;
setParentText: (param: string) => void;
};
export function ChildComponent({ text, setParentText }: ChildComponentProps) {
const changeParentText = () => {
setParentText("从子组件触发改变的文本");
};
return (
<Card
title="子组件"
style={{ width: 300, height: 300 }}
extra={
<Button type="primary" onClick={changeParentText}>
更改
</Button>
}
>
{text}
</Card>
);
}
兄弟通信
可以看作是对父子通信和子父通信的巧妙结合,让父组件成为数据的中转站。 即将子组件A中展示的数据存在父组件中,然后将更改该数据的方法通过props的形式传递给兄弟组件。
这样兄弟组件B如果对该数据做了改变,则子组件A便可以接收到这份从兄弟组件B那改变的数据。
graph TD
父组件 --props--> 子组件A
父组件 --props传递函数--> 子组件B
子组件B --将改变的数据作为函数入参--> 父组件
import { Card, Button } from "antd";
export type BrotherComponentProps = {
setParentText: (param: string) => void;
};
export function BrotherComponent({ setParentText }: BrotherComponentProps) {
const changeParentText = () => {
setParentText("从兄弟组件触发改变的文本");
};
return (
<Card
title="兄弟组件"
style={{ width: 300, height: 300 }}
extra={
<Button type="primary" onClick={changeParentText}>
更改
</Button>
}
></Card>
);
}
前面的部分我们简单地回忆了一遍父子通信、子父通信、兄弟通信。我们不难发现这些通信方式存在的问题———当组件树的深度越来越大的时候,通过props传递数据将会变得越来越繁琐。
如果还是以text字段为例,当组件树的深度如上图所示时,我们需要为props经过的每一层组件的propsType都声明一个接收text值的字段,并将其传给当前层级组件包含着的子组件,如此重复,直到嵌套结束。
这实在是太痛苦了😫,颇有点隔壁回调地狱的味道。
hello(() => {
bye(() => {
hello(() => {
bye(() => {
hello(() => {
fine()
})
})
})
})
})
为了走出这种困境,我们迎来了一些新的组件通信方式。
Context
这里的内容以后再来探索吧【派蒙.jpg】