React中不使用props如何实现子组件间的数据传递
这个需求完全具备可行性。React没有内置Vue实例上自带的$emit/$on全局事件API,但有多种成熟方案可以实现完全一致的无嵌套关系子组件间的数据传递效果,以下是两种最常用的实现方式:
方案1:自定义事件总线(和Vue使用逻辑最接近)
这个方案的使用体验和你在Vue中写的事件触发、监听逻辑几乎无差别,不需要引入额外依赖,适合中小项目快速实现跨组件通信。
首先新建一个公共的事件总线工具模块,本质是一个实现了事件订阅、发布、取消订阅能力的普通JS对象:
// src/utils/eventBus.js const eventBus = { eventMap: new Map(), // 等价于Vue的$on:注册事件监听 on(eventName, callback) { if (!this.eventMap.has(eventName)) { this.eventMap.set(eventName, []) } this.eventMap.get(eventName).push(callback) }, // 等价于Vue的$emit:触发事件并传递参数 emit(eventName, payload) { const callbackList = this.eventMap.get(eventName) if (callbackList) { callbackList.forEach(cb => cb(payload)) } }, // 等价于Vue的$off:移除事件监听 off(eventName, callback) { const callbackList = this.eventMap.get(eventName) if (callbackList) { const cbIndex = callbackList.indexOf(callback) if (cbIndex > -1) callbackList.splice(cbIndex, 1) } } } export default eventBus
在需要向外发送数据的子组件中,直接调用emit方法触发事件传值即可,和你之前写的Vue代码逻辑完全一致:
// 发送数据的子组件 import eventBus from '@/utils/eventBus' export default function Sender() { const handleSend = () => { const payload = { text: "测试传递数据", time: Date.now() } // 触发send事件,传递payload eventBus.emit("send", payload) } return <button onClick={handleSend}>点击发送数据到其他组件</button> }
在需要接收数据的组件中,组件挂载时注册事件监听,组件卸载时必须移除监听,避免内存泄漏和回调重复触发:
// 接收数据的组件 import { useState, useEffect } from 'react' import eventBus from '@/utils/eventBus' export default function Receiver() { const [receiveData, setReceiveData] = useState(null) useEffect(() => { // 定义事件回调 const handleSendEvent = (payload) => { // 在这里写接收到数据后的业务逻辑 console.log("收到跨组件传递的数据:", payload) setReceiveData(payload) } // 注册send事件监听 eventBus.on("send", handleSendEvent) // 组件卸载时移除监听 return () => { eventBus.off("send", handleSendEvent) } }, []) return ( <div> 接收到的内容:{receiveData ? JSON.stringify(receiveData) : "暂无数据"} </div> ) }
注意:和Vue组件实例自带的事件系统不同,自定义全局事件总线不会随组件卸载自动解绑事件,必须手动调用
off方法清理回调,否则会出现内存泄漏、事件重复触发的问题。
方案2:Context + 状态提升(React官方推荐,适合中大型项目)
如果项目规模较大,全局事件总线容易出现事件溯源困难、逻辑分散的问题,更推荐使用React原生的Context能力实现跨组件通信,逻辑更可追溯,也不需要手动处理事件绑定解绑。
核心思路是:把两个子组件共享的数据、修改数据的方法提升到最近的公共父组件,通过Context跨层下发给需要的子组件。
首先创建公共Context和对应的Provider、自定义hook:
// src/context/SendContext.js import { createContext, useContext, useState } from 'react' const SendContext = createContext(null) // 包裹公共父组件的Provider export function SendProvider({ children }) { const [payload, setPayload] = useState(null) // send方法等价于$emit,调用后更新共享状态 const send = (data) => setPayload(data) return ( <SendContext.Provider value={{ payload, send }}> {children} </SendContext.Provider> ) } // 子组件调用的hook,用来获取共享值和方法 export function useSendContext() { return useContext(SendContext) }
在两个子组件的公共父组件中,用SendProvider包裹需要通信的子组件:
// 公共父组件 import { SendProvider } from '@/context/SendContext' import Sender from './Sender' import Receiver from './Receiver' export default function Parent() { return ( <SendProvider> {/* 两个没有直接传值关系的子组件 */} <Sender /> <Receiver /> </SendProvider> ) }
发送数据的子组件中,取出send方法调用即可传值:
// 发送数据的子组件 import { useSendContext } from '@/context/SendContext' export default function Sender() { const { send } = useSendContext() const handleClick = () => { const payload = { text: "Context方式传值", time: Date.now() } send(payload) } return <button onClick={handleClick}>通过Context发送数据</button> }
接收数据的组件直接取出共享的payload即可,数据更新时组件会自动重渲染,不需要额外绑定事件:
// 接收数据的子组件 import { useSendContext } from '@/context/SendContext' export default function Receiver() { const { payload } = useSendContext() return ( <div> 接收到的内容:{payload ? JSON.stringify(payload) : "暂无数据"} </div> ) }
如果项目已经接入了Redux、Zustand、Jotai这类全局状态管理库,直接把共享状态存入全局Store,通过修改Store状态的动作触发更新、组件订阅Store状态变化的方式,也可以实现同等效果,适合更复杂的跨组件状态共享场景。
内容的提问来源于stack exchange,提问作者Karthik

