React 组件通讯全攻略:拒绝 "Props" 焦虑,掌握数据流动的艺术
前言
在 React 的开发旅程中,组件就像是乐高积木,我们把它们一个个搭起来构建出复杂的页面。但光搭起来还不够,这些积木之间必须有“电流”流通——这就是数据。
React 的设计哲学是单向数据流,就像瀑布一样,水流默认只能从高处流向低处。但在实际业务中,我们经常需要逆流而上,或者在两个平行的水池间交换水流。今天我们就来盘点 React 中最主流的四种“引水”方式,打通你的组件经脉。
1. 父传子:顺流而下的 Props
这是 React 中最自然、最基础的通信方式。想象一下,父亲给孩子零花钱,父亲(父组件)只需要要把钱(数据)递过去,孩子(子组件)伸手接着就行。
在代码层面,父组件通过在子组件标签上写上自定义属性(msg)来传递数据。
import Child from "./Child"
export default function Parent() {
const state = {
name: '小饶'
}
return (
<div>
<h2>父组件</h2>
{/* 父亲把 '小饶' 这个名字打包进 msg 属性传给孩子 */}
<Child msg={state.name} />
</div>
)
}
孩子组件这边,所有接收到的礼物都装在一个叫 props 的盒子里。不过要注意,Props 是只读的——这意味着孩子只能使用这些数据,不能直接修改它。就像孩子不能自己修改父亲银行卡的余额一样,如果要改,必须请求父亲操作。
export default function Child(props) {
// 打开盒子看看收到了什么
console.log(props);
return (
<h3>子组件 -- {props.msg}</h3>
)
}
2. 子传父:给孩子一个“遥控器”
既然水流默认向下,那子组件想要改变父组件的数据该怎么办?比如,孩子想告诉父亲:“我考了 100 分,请更新一下你的心情状态”。
这时候,父组件需要提前准备一个“遥控器”——也就是一个函数。父组件把这个函数通过 props 传给子组件,当子组件需要通信时,就按下这个遥控器(调用函数),并把数据作为参数传回去。
**父组件: 准备好 getNum 函数,用来接收数据并更新自己的 count。 **
import Child from "./Child"
import { useState } from 'react'
export default function Parent() {
let [count, setCount] = useState(1)
// 这就是那个“遥控器”函数
const getNum = (n) => {
setCount(n)
}
return (
<div>
<h2>父组件二 -- {count}</h2>
{/* 把遥控器交给孩子 */}
<Child getNum={getNum}></Child>
</div>
)
}
子组件: 在合适的时机(比如点击按钮时),通过 props 拿到并按下这个“遥控器”。
export default function Child(props) {
const state = {
num: 100
}
function send() {
// 此时调用的是父组件里的函数,把 100 传了回去
props.getNum(state.num)
}
return (
<div>
<h3>子组件二</h3>
<button onClick={send}>发送</button>
</div>
)
}
3. 兄弟组件:找个共同的“家长”
兄弟组件之间没有直接的连线,就像你和你的表弟住在不同的屋子里,想聊天得通过大厅里的长辈传话。 这种模式在 React 中通常被称为状态提升。既然 Brother1 想要给 Brother2 传值,那我们就把这个值保存在他们共同的父亲身上。
- Brother1 -> Parent:Brother1 先把数据传给父亲(利用上面的子传父技巧)。
- Parent -> Brother2:父亲拿到数据后,更新自己的状态,再把这个新状态顺手传给 Brother2(利用父传子技巧)。
父组件(中间枢纽):
import { useState } from "react"
import Child1 from "./Child1"
import Child2 from "./Child2"
export default function Parent() {
let [message, setMessage] = useState()
// 接收老大传来的消息
const getMsg = (msg) => {
setMessage(msg)
}
return (
<div>
<h2> 父组件三 </h2>
{/* 接收者:从 Child1 收信 */}
<Child1 getMsg={getMsg} />
{/* 发送者:把信转交给 Child2 */}
<Child2 message={message} />
</div>
)
}
Child1(消息发送方):
export default function Child1(props) {
const state = {
msg: '1 中的数据'
}
function send() {
props.getMsg(state.msg)
}
return (
<div>
<h3>子组件1</h3>
<button onClick={send}>1</button>
</div>
)
}
Child2(消息接收方):
export default function Child2(props) {
return (
<div>
{/* 坐等父亲把兄弟的消息送过来 */}
<h3>子组件2 --- {props.message}</h3>
</div>
)
}
4. 跨代组件通信:Context 传送门
如果组件层级很深,比如“爷爷 -> 爸爸 -> 儿子 -> 孙子”,如果还用 Props 一层层传,那中间的爸爸和儿子就成了无辜的“搬运工”,代码会变得非常臃肿麻烦。
为了解决这个问题,React 提供了一个 Context(上下文)机制。这就像在家族里设立了一个“广播站”,爷爷在顶层广播,底下的任何一代子孙,只要想听,就可以直接接收信号,完全不需要中间人转手。
爷组件(数据源头):
我们需要先 createContext 创建一个信号塔,然后用 <Context.Provider> 把所有后代包起来,value 就是我们要广播的数据。
import Parent from "./Parent"
import { createContext } from 'react'
export const Context = createContext() // 1. 建立信号塔
export default function Grand() {
return (
<div>
<h2> 爷组件 </h2>
{/* 2. 发射信号,内容是 value 中的数据 */}
<Context.Provider value= {'爷组件的数据'}>
<Parent/>
</Context.Provider>
</div>
)
}
父组件(路人甲):
你看,父组件完全不需要碰这些数据,它只需要安静地渲染它的子组件即可。
import Child from "./Child"
export default function Parent() {
return (
<div>
<h3>父组件</h3>
<Child></Child>
</div>
)
}
孙子组件(数据接收者):
孙子组件不需要管它离爷爷隔了多少代,直接用 useContext 这个钩子函数,就能连上信号塔拿到数据。
import { useContext } from 'react'
import { Context } from './Grand' // 3. 引入信号塔定义
export default function Child() {
// 4. 接收信号
const msg = useContext(Context)
return (
<div>
<h4>孙子组件 --- {msg}</h4>
</div>
)
}
结语
组件通信是 React 开发中最基本也最重要的内功。
- 简单的父子关系,Props 和 Callback 是最轻量的选择;
- 兄弟组件,记得找共同的父级帮忙周转;
- 当层级太深感到繁琐时,Context 就是你的救星。
掌握了这四招,你就能从容应对绝大多数的组件交互场景,让数据在你的应用中流动得井井有条。