【React组件通讯双重视角】函数式 vs 类式开发指南
目录
前言
正文
父组件向子组件传值
函数式写法
类式写法
子组件向父组件传值
函数式写法
类式写法
兄弟组件通信
函数式写法
类式写法
跨层级通信(使用Context)
函数式写法
类式写法
进阶通讯方式(补充说明)
一、事件总线:使用 EventEmitter
实现步骤:
二、Ref 传递:forwardRef + useImperativeHandle
实现步骤:
总结
事件总线(EventEmitter):
Ref 传递(forwardRef + useImperativeHandle):
结语
前言
在现代前端开发中,React 作为最流行的 JavaScript 库之一,以其组件化、声明式编程和高性能的特点,成为构建用户界面的首选工具。然而,随着应用复杂度的提升,组件之间的通信问题逐渐成为开发者需要面对的核心挑战之一。无论是父子组件之间的数据传递,还是跨层级组件的状态共享,如何高效、优雅地实现组件间的通信,直接影响到代码的可维护性和应用的性能。
React 提供了多种组件通信的方式,以下是几种常见的模式:
-
Props 传递:父组件通过 props 向子组件传递数据。
-
回调函数:子组件通过回调函数向父组件传递数据。
-
Context API:用于跨层级组件之间的数据共享。
-
状态提升:将共享状态提升到共同的父组件中。
-
Ref 和事件机制:用于直接操作组件或触发事件。
接下来,我们将从 函数式组件 和 类式组件 两个角度,详细讲解这些通信方式的具体实现。
正文
父组件向子组件传值
函数式写法
// 父组件
function ParentComponent() {
const [message] = useState('来自父组件的消息');
return <ChildComponent message={message} />;
}
// 子组件
function ChildComponent({ message }) {
return <div>{message}</div>;
}
类式写法
// 父组件
class ParentComponent extends React.Component {
state = { message: '来自父组件的消息' };
render() {
return <ChildComponent message={this.state.message} />;
}
}
// 子组件
class ChildComponent extends React.Component {
render() {
return <div>{this.props.message}</div>;
}
}
子组件向父组件传值
函数式写法
// 父组件
function ParentComponent() {
const handleChildData = (data) => {
console.log('收到子组件数据:', data);
};
return <ChildComponent sendData={handleChildData} />;
}
// 子组件
function ChildComponent({ sendData }) {
const sendMessage = () => {
sendData('子组件发送的消息');
};
return <button onClick={sendMessage}>发送消息</button>;
}
类式写法
// 父组件
class ParentComponent extends React.Component {
handleChildData = (data) => {
console.log('收到子组件数据:', data);
};
render() {
return <ChildComponent sendData={this.handleChildData} />;
}
}
// 子组件
class ChildComponent extends React.Component {
sendMessage = () => {
this.props.sendData('子组件发送的消息');
};
render() {
return <button onClick={this.sendMessage}>发送消息</button>;
}
}
兄弟组件通信
函数式写法
function Parent() {
const [sharedData, setSharedData] = useState('');
return (
<>
<SiblingA setData={setSharedData} />
<SiblingB data={sharedData} />
</>
);
}
function SiblingA({ setData }) {
return <input onChange={(e) => setData(e.target.value)} />;
}
function SiblingB({ data }) {
return <div>接收到的数据: {data}</div>;
}
类式写法
class Parent extends React.Component {
state = { sharedData: '' };
setSharedData = (data) => {
this.setState({ sharedData: data });
};
render() {
return (
<>
<SiblingA setData={this.setSharedData} />
<SiblingB data={this.state.sharedData} />
</>
);
}
}
class SiblingA extends React.Component {
handleChange = (e) => {
this.props.setData(e.target.value);
};
render() {
return <input onChange={this.handleChange} />;
}
}
class SiblingB extends React.Component {
render() {
return <div>接收到的数据: {this.props.data}</div>;
}
}
跨层级通信(使用Context)
函数式写法
const MyContext = createContext();
function App() {
return (
<MyContext.Provider value="全局数据">
<MiddleComponent />
</MyContext.Provider>
);
}
function MiddleComponent() {
return <ChildComponent />;
}
function ChildComponent() {
const value = useContext(MyContext);
return <div>{value}</div>;
}
类式写法
const MyContext = React.createContext();
class App extends React.Component {
render() {
return (
<MyContext.Provider value="全局数据">
<MiddleComponent />
</MyContext.Provider>
);
}
}
class MiddleComponent extends React.Component {
render() {
return <ChildComponent />;
}
}
class ChildComponent extends React.Component {
static contextType = MyContext;
render() {
return <div>{this.context}</div>;
}
}
进阶通讯方式(补充说明)
-
状态管理方案:Redux/MobX
-
事件总线:使用EventEmitter
-
Ref传递:forwardRef + useImperativeHandle
-
状态库:Recoil/Zustand
一、事件总线
事件总线是一种跨组件通信的方式,适用于任意组件之间的通信,尤其是非父子关系的组件。通过事件总线,组件可以订阅和触发事件,从而实现数据传递。
这里用了一个mitt,所以要下载一个依赖
npm i mitt --save
实现步骤:
-
创建一个全局的事件总线。
-
在需要接收数据的组件中订阅事件。
-
在需要发送数据的组件中触发事件。
import React, { useEffect, useState } from 'react';
import mitt from 'mitt'
// 创建全局事件总线
const eventBus = mitt()
// 组件A:发送事件
function ComponentA() {
const sendMessage = () => {
eventBus.emit('message', 'Hello from ComponentA!')
}
return (
<div>
<button onClick={sendMessage}>发送消息</button>
</div>
)
}
// 组件B:接收事件
function ComponentB() {
const [message, setMessage] = useState('')
useEffect(() => {
// 订阅事件
function isString(value: any): value is string {
return typeof value === 'string'
}
const handleMessage = (data: any) => {
if (isString(data)) {
setMessage(data)
}
}
eventBus.on('message', handleMessage)
// 清理订阅
return () => {
eventBus.off('message', handleMessage)
}
}, [])
return (
<div>
<p>接收到的消息: {message}</p>
</div>
)
}
// 父组件
function App() {
return (
<div>
<ComponentA />
<ComponentB />
</div>
);
}
export default App;
二、Ref 传递:forwardRef + useImperativeHandle
forwardRef
和 useImperativeHandle
是 React 提供的用于操作子组件实例的 API。通过它们,父组件可以访问子组件的特定方法或属性。
实现步骤:
-
使用
forwardRef
包裹子组件,使其能够接收ref
。 -
在子组件中使用
useImperativeHandle
暴露特定的方法或属性。 -
在父组件中通过
ref
调用子组件的方法。
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
interface ChildComponentRef {
increment: () => void
getCount: () => number
}
// 子组件
const ChildComponent = forwardRef<ChildComponentRef>((props, ref) => {
const [count, setCount] = useState(0)
// 暴露方法给父组件
useImperativeHandle(ref, () => ({
increment: () => {
setCount((prevCount) => prevCount + 1)
},
getCount: () => {
return count
},
}))
return (
<div>
<p>子组件计数: {count}</p>
</div>
)
})
// 父组件
function ParentComponent() {
const childRef = useRef<ChildComponentRef>(null)
const handleIncrement = () => {
if (childRef.current) {
childRef.current.increment() // 调用子组件的 increment 方法
}
}
const handleGetCount = () => {
if (childRef.current) {
alert('当前计数: ' + childRef.current.getCount()) // 调用子组件的 getCount 方法
}
}
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleIncrement}>增加计数</button>
<button onClick={handleGetCount}>获取计数</button>
</div>
)
}
export default ParentComponent;
总结
事件总线(EventEmitter):
-
适用于任意组件之间的通信。
-
需要手动管理事件的订阅和清理。
-
适合非父子关系的组件通信。
Ref 传递(forwardRef + useImperativeHandle):
-
适用于父组件需要直接操作子组件方法或属性的场景。
-
通过
useImperativeHandle
暴露特定的方法,保持组件的封装性。 -
适合需要直接操作 DOM 或子组件逻辑的场景。
结语
希望本文的内容对你有用呦!