React 组件通信

1. 父子组件的通信方式

props是最为常用的父子组件通信方式

直接上示例代码了

Father.js 父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 父组件

import React, { Component } from "react";
import Son from "./Son";
import { getRandomNumber } from "../utils/Random";

// 类组件的写法
export default class Father extends Component {
state = {
msg: "",
sonTome: "",
};
callback = (data) => {
this.setState({
sonTome: data,
});
};
render() {
return (
<div
style={{
backgroundColor: "skyblue",
}}
>
<h2>父组件</h2>
<p>子组件的数据:{this.state.sonTome}</p>
<button
onClick={() => {
this.setState({
msg: '法拉利' + getRandomNumber(1, 100),
});
}}
>
点击向儿子发送我的法拉利
</button>
<Son msg={this.state.msg} cb={this.callback}/>
</div>
);
}
}

Son.js 子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 子组件

import React, { Component } from "react";
import { getRandomNumber } from "../utils/Random";
// 类组件的写法
export default class Son extends Component {
state = {
msg : "奥特曼玩具"
}
render() {
return (
<div
style={{
backgroundColor: `rgb(99,99,48)`,
}}
>
<h2>子组件</h2>
<button onClick={() => {
this.props.cb(this.state.msg + getRandomNumber(1, 100))
}}>点击向父亲发送奥特曼玩具</button>
<p>{this.props.msg}</p>
</div>
);
}
}

通信的结果如下

父子组件通信

父组件与子组件之间使用props进行通信,遵从单向数据流的原则。

谁的数据谁维护。

还有一个很重要的点props是只读的,这意味着不能直接修改父组件传递过来的props,可以将其缓存,作为自己的状态来进行维护。

单向数据流

在 React 中,单向数据流意味着状态始终从上层组件传递到下层组件。也就是说,父组件可以将状态通过 props 传递给子组件,但子组件不能直接修改从父组件接收的 props。这样可以确保数据的一致性和可预测性,使得应用的状态更易于管理。

如果子组件需要修改父组件的状态,通常的做法是父组件将一个回调函数通过 props 传递给子组件,子组件调用这个回调函数来通知父组件更新状态。这仍然遵循了单向数据流的原则,因为数据仍然是从父组件流向子组件,子组件只是通过调用回调函数来触发父组件的状态更新。

父子组件通信

2. 兄弟组件通信

这个实现也很简单,理解了子传父的方法,就可以基于同一个父组件来实现兄弟组件之间的通信

依照我的理解,大致是这样的

兄弟组件通信

BrotherA.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { Component } from "react";

export default class BrotherA extends Component {
message = "来自BrotherA组件的消息"
render() {
return (
<div style={{
backgroundColor: "lightgreen",
}}>
<h2>我是BrotherA组件</h2>
<button onClick={() => this.props.fn(this.message)}>点击给BrotherB组件传递消息</button>
</div>
);
}
}

BrotherB.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from "react";

export default class BrotherB extends Component {
render() {
return (
<div style={{
backgroundColor: "lightyellow",
}}>
<h2>我是BrotherB组件</h2>
<p>我是BrotherB组件的内容:{this.props.message}</p>
</div>
);
}
}

他们的 Father.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React, { Component } from "react";
import BrotherA from "./BrotherA";
import BrotherB from "./BrotherB";
export default class Father extends Component {
state = {
message: "说谢谢",
};
changeMessage = (message) => {
console.log("父组件收到了:" + message);
this.setState({
message,
});
};
render() {
return (
<div style={{
backgroundColor: "lightblue",
}}>
<h1>父组件</h1>

<BrotherA fn={this.changeMessage}/>
<BrotherB message={this.state.message}/>
</div>
);
}
}

通信效果

兄弟组件通信

这种方案实在是有点憋屈,兄弟之间对话还要通过中间人进行数据传递,一点隐私都没有啊!肯定还有更好的解决方案

3. Context

3.1 概述

context通常用于父子组件祖孙组件全局状态管理。父子组件通信还是props比较方便,官方文档上说的是,遇到组件通信的问题还是先使用props比较好,因为这样能清晰的追踪数据的流向。但是“远距离”通信确实啰里啰唆的。

在使用过程中,context 更像是创造了俩东西,我把这俩东西一个想成是工厂用来生成我们的产品(Provider 组件提供数据),一个是消费者消费产品(Consumer 组件接收数据,并且使用数据)。

3.2 作用域

作用范围

  • Context 的作用范围是从Provider组件开始,一直到它的子组件树中的任意深度的子组件。
  • 只有被Provider组件包裹的组件(包括Provider的直接子组件和所有后代组件)才能访问到这个 Context 的值。
  • 如果一个组件不在Provider的包裹范围内,那么它就无法访问到这个 Context 的值。

组件树和子树

  • 可以把 React 应用看作是一个组件树,每个组件都是树上的一个节点。
  • 当你在某个节点上放置一个Provider组件时,你就为这个节点及其所有后代节点创建了一个“子树”。
  • 这个子树中的所有组件都可以访问到这个Provider提供的 Context 的值。

下面是一个小 demo

这个 demo 的组件之间的关系是这样的

context1

真是嵌了一套又一套啊,这里准备在使用 Provider 组件包裹我们的 A 组件,相当于以 A 的上一层组件为根节点,创建了一颗子组件树。

关于 context 的创建,代码如下

这里使用了 MyContext 来保存 context 的数据还有回调,使用useCallback这个 Hook(useCallback 是 React 的一个 Hook,它主要用于返回一个记忆化的回调函数。这个 Hook 的主要目的是在依赖项(例如 props 或 state)未发生变化时,避免不必要的重新渲染和重新创建回调函数,从而提高应用性能。)

这里将 Provider 组件进行了简单的封装,一是添加了原始的数据 data,并且添加了一个回调 handleDataFormC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建Context
import React, { createContext, useState, useCallback } from "react";

const MyContext = createContext(null);

// 提供Context的Provider组件
const MyContextProvider = ({ children }) => {
const [data, setData] = useState("最原始的数据");
const handleDataFromC = useCallback((newData) => {
setData(newData);
}, []);

return (
<MyContext.Provider value={{ data, setDataFromA: handleDataFromC }}>
{children}
</MyContext.Provider>
);
};

export { MyContext, MyContextProvider };

被 MyContextProvider 包裹住的地方,这里为了方便自己写 demo,把它放在了 Layout 组件下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react";
import { MyContextProvider } from "./MyContext";
import A from "./A";
import B from "./B";
export default function Layout() {
return (
<div
style={{
backgroundColor: "#f1f2f3f4",
borderRadius: "20px",
textAlign: "center",
}}
>
<MyContextProvider>
<A />
<B />
</MyContextProvider>
</div>
);
}

A 组件

A 组件使用了 context 提供的 data 还有回调,当点击更新A组件的数据这个按钮的时候,data 应该会被更新为Data updated from A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// A组件
import React from "react";
import { MyContext } from "./MyContext";
const A = () => {
return (
<MyContext.Consumer>
{({ data, setDataFromA }) => (
<div>
<h2>A组件</h2>
<p>A组件的数据: {data}</p>
<button onClick={() => setDataFromA("Data updated from A")}>
更新A组件的数据
</button>
</div>
)}
</MyContext.Consumer>
);
};
export default A;

B 组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// B组件
import React from "react";
import C from "./C";

const B = () => {
return (
<div style={{
backgroundColor: "#fad3f5f6",
}}>
<h2>B组件</h2>
<C />
</div>
);
};
export default B;

C 组件

通过使用 context 中的回调,去和 A 组件进行通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// C组件
import React, { useContext, useState } from "react";
import { MyContext } from "./MyContext";

const C = () => {
const { data, setDataFromA } = useContext(MyContext);
const [localData, setLocalData] = useState("");

const handleDataChange = () => {
setLocalData(`${Date.now()}`);
// 调用A组件提供的回调函数,将数据发送回A
setDataFromA(localData);
};

return (
<div style={{
backgroundColor: "#f5f6d3",
}}>
<h2>C组件</h2>
<p>A组件的数据: {data}</p>
<p>C组件的数据: {localData}</p>
<button onClick={handleDataChange}>发送数据给A组件</button>
</div>
);
};
export default C;

通信的效果如下:

context.2