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 的组件之间的关系是这样的
真是嵌了一套又一套啊,这里准备在使用 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;
|
通信的效果如下: