组件间的通信,就像人与人的对话——单向传递是尊重,状态提升是信任,而全局共享则是无需言语的默契。
本文是《从0死磕全栈》系列第6篇,系统梳理 React + TypeScript 开发中六大核心组件通信方式,涵盖父子传值、兄弟通信、跨层级共享、全局状态管理及 Refs 直接操控,助你根据场景精准选择,告别“props 一层层传到秃头”的困境。
一、第一种:父组件向子组件传值(Props)
✅ 适用场景
父组件需要向子组件传递静态或动态数据(如标题、配置、初始值)。
✅ 实现方式
通过 props 单向传递,并使用 TypeScript 接口定义类型,确保安全性和可维护性。
// 父组件
function Parent() {
const message: string = "Hello from Parent!";
return <Child message={message} />;
}
// 子组件 Props 类型定义
interface ChildProps {
message: string;
}
// 子组件
function Child({ message }: ChildProps) {
return <h1>{message}</h1>; // 输出: Hello from Parent!
}
🔍 关键要点
- 单向数据流:数据只能从父流向子,符合 React 设计哲学。
- TypeScript 友好:使用
interface定义 props 类型,编译期校验,避免运行时错误。 - 可扩展性强:新增属性只需在接口中添加,不影响逻辑。
二、第二种:子组件向父组件传值(回调函数)
✅ 适用场景
子组件触发事件(如按钮点击、表单提交)需通知父组件并传递数据。
✅ 实现方式
父组件传递一个回调函数作为 prop,子组件调用该函数并传参。
// 父组件
function Parent() {
const handleChildData = (data: string): void => {
console.log("Data from Child:", data); // 输出: Data from Child: Hello from Child!
};
return <Child onSendData={handleChildData} />;
}
// 子组件 Props 类型
interface ChildProps {
onSendData: (data: string) => void;
}
// 子组件
function Child({ onSendData }: ChildProps) {
const sendData = (): void => {
onSendData("Hello from Child!");
};
return <button onClick={sendData}>Send Data</button>;
}
💡 比喻理解
父组件提供了一个“收件箱”(回调函数),子组件把信息“投递”进去,父组件收到后自动处理。
三、第三种:兄弟组件通信(状态提升)
✅ 适用场景
两个同级组件(兄弟)需要共享和同步数据(如 A 显示内容,B 修改内容)。
✅ 实现方式
将共享状态提升到共同父组件,再通过 props 分别传递给子组件。
// 父组件
function Parent() {
const [sharedData, setSharedData] = useState<string>("Initial Data");
return (
<div>
<SiblingA data={sharedData} />
<SiblingB onUpdateData={setSharedData} />
</div>
);
}
// 兄弟组件 A(显示)
interface SiblingAProps {
data: string;
}
function SiblingA({ data }: SiblingAProps) {
return <p>Data from SiblingB: {data}</p>;
}
// 兄弟组件 B(更新)
interface SiblingBProps {
onUpdateData: (data: string) => void;
}
function SiblingB({ onUpdateData }: SiblingBProps) {
return (
<button onClick={() => onUpdateData("Updated Data")}>
Update Data
</button>
);
}
⚠️ 注意事项
- 仅适用于少量共享状态,若兄弟组件过多或状态复杂,应改用 Context 或 Zustand。
- 避免“状态过度提升”,导致父组件臃肿。
四、第四种:跨层级组件通信(Context API)
✅ 适用场景
深层嵌套组件(如主题、用户登录态、语言设置)需要共享数据,避免逐层 props 传递。
✅ 实现方式
使用 React.createContext 创建上下文,通过 Provider 提供,useContext 消费。
// 1. 定义 Context 类型
interface ThemeContextType {
theme: string;
setTheme: (theme: string) => void;
}
// 2. 创建 Context(带默认值)
const ThemeContext = React.createContext<ThemeContextType>({
theme: "light",
setTheme: () => {},
});
// 3. 父组件(Provider)
function App() {
const [theme, setTheme] = useState<string>("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<ChildComponent />
</ThemeContext.Provider>
);
}
// 4. 子组件(Consumer)
function ChildComponent() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<div>
<p>Current Theme: {theme}</p>
<button onClick={() => setTheme("dark")}>Toggle Theme</button>
</div>
);
}
✅ 优势
- 解决“props drilling”(属性钻取)问题
- 支持任意深度组件访问
- 与 React Suspense、Server Components 兼容性好
🚫 缺点
- 所有订阅组件都会因值变化重新渲染(可用
useMemo/useCallback优化) - 不适合高频变更的动态数据(如鼠标位置)
五、第五种:全局状态管理(Redux / Zustand)
✅ 适用场景
大型应用中多个无关组件共享复杂、持久化、异步的状态(如用户信息、购物车、主题、权限)。
✅ 推荐方案:Zustand(轻量首选)
// store/userStore.ts
import { create } from 'zustand';
interface UserState {
user: { id: number; name: string } | null;
login: (token: string) => void;
logout: () => void;
}
const useUserStore = create<UserState>((set) => ({
user: null,
login: (token) => set({ user: { id: 1, name: '张三' } }),
logout: () => set({ user: null }),
}));
export default useUserStore;
在任意组件中直接使用:
import useUserStore from '../store/userStore';
function Header() {
const { user, logout } = useUserStore();
return (
<div>
{user ? <span>欢迎, {user.name}</span> : <button onClick={logout}>登录</button>}
</div>
);
}
✅ 对比 Redux Toolkit
| 特性 | Zustand | Redux Toolkit |
|---|---|---|
| 包大小 | ~1KB | ~50KB+ |
| 配置 | 无 Provider,开箱即用 | 需 Store + Provider + Slice |
| 代码量 | 极简 | 较多样板 |
| 学习曲线 | 极低 | 中高 |
| 推荐场景 | 小中型项目 | 大型复杂企业应用 |
💡 结论:90% 的中小型项目,选 Zustand 足矣!
六、第六种:Refs 直接访问子组件
✅ 适用场景
父组件需要直接调用子组件的方法或访问 DOM 元素(如聚焦输入框、播放视频、触发动画)。
✅ 实现方式
使用 useRef + forwardRef + useImperativeHandle
// 1. 定义 Ref 类型
interface ChildRef {
increment: () => void;
}
// 2. 子组件(暴露方法)
const Child = forwardRef<ChildRef>((props, ref) => {
const [count, setCount] = useState<number>(0);
useImperativeHandle(ref, () => ({
increment() {
setCount(count + 1);
},
}));
return <p>Child Count: {count}</p>;
});
// 3. 父组件
function Parent() {
const childRef = useRef<ChildRef>(null);
return (
<div>
<Child ref={childRef} />
<button onClick={() => childRef.current?.increment()}>
Increment from Parent
</button>
</div>
);
}
⚠️ 使用原则
- 慎用! 这是“命令式”编程,违背 React 声明式理念。
- 仅用于无法通过 props/状态解决的底层操作(如 focus、scroll、动画控制)。
- 切勿用于数据传递!
七、总结:如何选择?
| 场景 | 推荐方案 |
|---|---|
| 简单数据传递(父→子) | ✅ Props |
| 子组件通知父组件 | ✅ 回调函数(Props) |
| 兄弟组件共享数据 | ✅ 状态提升(父组件管理) |
| 深层嵌套共享(主题、用户) | ✅ Context API |
| 多组件共享复杂状态(登录、购物车) | ✅ Zustand(推荐) / Redux Toolkit |
| 需要直接操作子组件 DOM/方法 | ✅ Refs(谨慎使用) |
📌 黄金法则
通信的终极目标不是传递数据,而是构建理解——让每个组件在正确的时间,以正确的方式,做正确的事。
🎯 结语:别让通信成为你的绊脚石
React 的强大不在于它能“做什么”,而在于它教会你“何时不该做”。
- 不要用 Context 做计数器;
- 不要用 Refs 传递用户名;
- 不要用 Zustand 管理输入框的值。
合适的工具,胜过强大的框架。
掌握这六种通信方式,你就能像一位指挥家一样,让每一个组件各司其职,和谐共鸣。
✅ 下一篇预告
《从0死磕全栈第7天:React useEffect 副作用起了大作用之实现购物车功能》
—— 深入理解副作用、依赖数组、清理机制,打造带 localStorage 持久化的购物车!
📌 推荐阅读