前言
公司来了一批新的实习生,有几位同学没有使用过React,在项目中使用React时遇到了跨组件通信的问题,在和新人传授经验的同时想着记录下来方便后面新人培训使用。
假设有A,B两个组件。模拟从A调用B的函数,进行操作或返回某个值
一、使用上下文(Context)和钩子(Hook)
借助 React 的上下文 API 和自定义钩子,能够实现跨层级的函数调用。
首先,创建一个上下文:
// context/FunctionContext.tsx
import React, { createContext, useContext, useState } from 'react';
type FunctionContextType = {
bFunction: () => void;
setBFunction: (fn: () => void) => void;
};
const FunctionContext = createContext<FunctionContextType | undefined>(undefined);
export const FunctionProvider = ({ children }: { children: React.ReactNode }) => {
const [bFunction, setBFunction] = useState<() => void>(() => () => {});
return (
<FunctionContext.Provider value={{ bFunction, setBFunction }}>
{children}
</FunctionContext.Provider>
);
};
export const useFunctionContext = () => {
const context = useContext(FunctionContext);
if (!context) {
throw new Error('useFunctionContext must be used within a FunctionProvider');
}
return context;
};
接着,在 B 组件中注册函数:
// components/BComponent.tsx
import React, { useEffect } from 'react';
import { useFunctionContext } from '../context/FunctionContext';
const BComponent = () => {
const { setBFunction } = useFunctionContext();
const handleClick = () => {
console.log('B组件的函数被调用了');
};
useEffect(() => {
setBFunction(handleClick);
return () => {
// 清理函数
setBFunction(() => () => {});
};
}, []);
return <div>B组件</div>;
};
export default BComponent;
最后,在 A 组件中调用 B 组件的函数:
// components/AComponent.tsx
import React from 'react';
import { useFunctionContext } from '../context/FunctionContext';
const AComponent = () => {
const { bFunction } = useFunctionContext();
const handleCallBFunction = () => {
bFunction();
};
return (
<div>
<button onClick={handleCallBFunction}>调用B组件的函数</button>
</div>
);
};
export default AComponent;
在页面中使用这些组件:
// pages/index.tsx
import React from 'react';
import { FunctionProvider } from '../context/FunctionContext';
import AComponent from '../components/AComponent';
import BComponent from '../components/BComponent';
const HomePage = () => {
return (
<FunctionProvider>
<div>
<AComponent />
<BComponent />
</div>
</FunctionProvider>
);
};
export default HomePage;
二、使用事件总线(Event Bus)
用过Vue的应该比较熟悉这个,不过event bus更适合用在函数调用,获取变量值推荐另两种方案。(也能获取变量值,比较麻烦需要注册两个事件)
创建一个全局事件总线,以此实现组件间的通信。
// utils/eventBus.ts
type EventHandler = (...args: any[]) => void;
class EventBus {
private events: Record<string, EventHandler[]> = {};
on(eventName: string, handler: EventHandler) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(handler);
}
off(eventName: string, handler: EventHandler) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(h => h !== handler);
}
}
emit(eventName: string, ...args: any[]) {
if (this.events[eventName]) {
this.events[eventName].forEach(handler => handler(...args));
}
}
}
export const eventBus = new EventBus();
B 组件监听事件:
// components/BComponent.tsx
import React, { useEffect } from 'react';
import { eventBus } from '../utils/eventBus';
const BComponent = () => {
const handleFunctionCall = () => {
console.log('B组件的函数被调用了');
};
useEffect(() => {
eventBus.on('callBFunction', handleFunctionCall);
return () => {
eventBus.off('callBFunction', handleFunctionCall);
};
}, []);
return <div>B组件</div>;
};
export default BComponent;
A 组件触发事件:
// components/AComponent.tsx
import React from 'react';
import { eventBus } from '../utils/eventBus';
const AComponent = () => {
const handleCallBFunction = () => {
eventBus.emit('callBFunction');
};
return (
<div>
<button onClick={handleCallBFunction}>调用B组件的函数</button>
</div>
);
};
export default AComponent;
三、使用状态管理库(如 Redux 或 Zustand)
以 Zustand 为例
创建Store
// store/functionStore.ts
import create from 'zustand';
type FunctionStore = {
bFunction: () => void;
setBFunction: (fn: () => void) => void;
};
export const useFunctionStore = create<FunctionStore>((set) => ({
bFunction: () => {},
setBFunction: (fn) => set({ bFunction: fn }),
}));
B 组件注册函数:
// components/BComponent.tsx
import React, { useEffect } from 'react';
import { useFunctionStore } from '../store/functionStore';
const BComponent = () => {
const setBFunction = useFunctionStore((state) => state.setBFunction);
const handleClick = () => {
console.log('B组件的函数被调用了');
};
useEffect(() => {
setBFunction(handleClick);
return () => {
setBFunction(() => () => {});
};
}, [setBFunction]);
return <div>B组件</div>;
};
export default BComponent;
A 组件调用函数:
// components/AComponent.tsx
import React from 'react';
import { useFunctionStore } from '../store/functionStore';
const AComponent = () => {
const bFunction = useFunctionStore((state) => state.bFunction);
const handleCallBFunction = () => {
bFunction();
};
return (
<div>
<button onClick={handleCallBFunction}>调用B组件的函数</button>
</div>
);
};
export default AComponent;
总结和方案选择
- 当组件层级较深时,建议使用上下文(Context)。
- 若组件关系灵活,事件总线更为合适。
- 对于复杂的应用场景,状态管理库是不错的选择。
要避免在函数式组件中使用 refs,因为它不太适合这种场景。