188 lines
4.8 KiB
Markdown
188 lines
4.8 KiB
Markdown
---
|
||
tags: [React, Component Communication, Props, Context, Frontend]
|
||
create time: 2026-04-29 22:10
|
||
---
|
||
|
||
# 组件通信模式
|
||
|
||
## 概述
|
||
|
||
React 中父子组件之间的数据流动有多种方式。理解每种模式的适用场景和性能影响,能够避免过度设计或通信瓶颈。本文档从简单到复杂,梳理完整的通信方案谱系。
|
||
|
||
## 通信方向全景图
|
||
|
||
```mermaid
|
||
graph TD
|
||
A["父 → 子"] --> B["Props(最基础)"]
|
||
A --> C["Context Provider(跨层级)"]
|
||
A --> D["状态管理库(全局共享)"]
|
||
|
||
E["子 → 父"] --> F["回调 Prop(事件驱动)"]
|
||
E --> G["refs(imperative)"]
|
||
|
||
H["兄弟组件"] --> I["状态提升到共同父组件"]
|
||
H --> J["通过共同祖先通信"]
|
||
H --> K["状态管理库 / 发布订阅"]
|
||
|
||
style B fill:#4FC08D,color:#fff
|
||
style C fill:#F5A87D,color:#000
|
||
style F fill:#61DAFB,color:#000
|
||
```
|
||
|
||
## Props Drill(属性逐层透传)
|
||
|
||
### 问题场景
|
||
|
||
```tsx
|
||
function App() {
|
||
const user = getUser();
|
||
return <Header user={user} />; // Header 不需要 user,但深层的 Avatar 需要!
|
||
}
|
||
|
||
function Header({ user }) {
|
||
return (
|
||
<nav>
|
||
<Logo /> {/* ❌ 无需 user,却接收了 */}
|
||
<Avatar user={user} /> {/* ✅ 需要 */}
|
||
</nav>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 解决方案对比
|
||
|
||
| 方法 | 适合场景 | 额外依赖 |
|
||
|------|----------|---------|
|
||
| **Context** | 少量深层传递(主题、用户信息) | 无 |
|
||
| **自定义 Hook + Props 重组织** | 中等深度(3-4 层) | 无 |
|
||
| **状态管理库** | 频繁变化、多组件共享 | Zustand/Redux |
|
||
| **Render Prop / HOC** | 复用逻辑而非传值 | 无 |
|
||
|
||
## Callback Prop(子 → 父)
|
||
|
||
```tsx
|
||
interface FormProps {
|
||
onSubmit: (data: FormValues) => Promise<void>;
|
||
}
|
||
|
||
function LoginForm({ onSubmit }: FormProps) {
|
||
const handleSubmit = (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
const data = collectFormData();
|
||
onSubmit(data); // 通知父组件
|
||
};
|
||
|
||
return <form onSubmit={handleSubmit}><Button type="submit">登录</Button></form>;
|
||
}
|
||
|
||
// 父组件
|
||
function App() {
|
||
const handleLogin = async (data: FormValues) => {
|
||
await api.login(data);
|
||
navigate("/dashboard");
|
||
};
|
||
|
||
return <LoginForm onSubmit={handleLogin} />;
|
||
}
|
||
```
|
||
|
||
> [!note] 为什么不用 props.children 做通信?
|
||
> children 是 UI 内容插槽,不是通信机制。通信用 callback prop 保持关注点分离。
|
||
|
||
## Context 跨层级通信
|
||
|
||
```tsx
|
||
const UserContext = createContext<User | null>(null);
|
||
|
||
function UserProfile({ children }: { children: React.ReactNode }) {
|
||
const user = useDatabaseUser(userId);
|
||
|
||
return (
|
||
<UserContext.Provider value={user}>
|
||
{/* Avatar、Settings、OrderHistory 任意深度都可消费 */}
|
||
{children}
|
||
</UserContext.Provider>
|
||
);
|
||
}
|
||
|
||
function OrderHistory() {
|
||
const user = useContext(UserContext); // 直接获取,无需中间层透传
|
||
return <div>{user?.orders?.map(...)}</div>;
|
||
}
|
||
```
|
||
|
||
> [!warning] Context 的性能陷阱
|
||
> - Provider value 对象每次渲染都是新引用 → 所有消费者重渲染
|
||
> - 解决:拆分 Context、或使用 `useReducer` 返回稳定的 `{state, dispatch}` 对象
|
||
|
||
## 状态管理库方案
|
||
|
||
```tsx
|
||
// Zustand 方案:任意两个组件共享 state,无需祖先后代关系
|
||
import { create } from "zustand";
|
||
|
||
const useCartStore = create((set) => ({
|
||
items: [],
|
||
addItem: (item) => set(state => ({ items: [...state.items, item] })),
|
||
}));
|
||
|
||
function ProductCard() {
|
||
const addItem = useCartStore(s => s.addItem);
|
||
return <button onClick={() => addItem(product)}>加入购物车</button>;
|
||
}
|
||
|
||
function CartIcon() {
|
||
const items = useCartStore(s => s.items);
|
||
return <Badge>{items.length}</Badge>;
|
||
}
|
||
// 两者毫无关联,但共享同一份状态
|
||
```
|
||
|
||
## Ref Imperative API(命令式通信)
|
||
|
||
```tsx
|
||
const formRef = useRef<FormHandle>(null);
|
||
|
||
function Parent() {
|
||
const handleSubmit = () => {
|
||
formRef.current?.validate(); // 命令子组件执行方法
|
||
formRef.current?.reset();
|
||
};
|
||
|
||
return <><ChildForm ref={formRef} /><button onClick={handleSubmit}>提交</button></>;
|
||
}
|
||
|
||
const ChildForm = forwardRef<FormHandle>((props, ref) => {
|
||
useImperativeHandle(ref, () => ({
|
||
validate: () => validator.validate(),
|
||
reset: () => setFields(initialValues),
|
||
}));
|
||
|
||
return <form>...</form>;
|
||
});
|
||
```
|
||
|
||
## 决策树
|
||
|
||
```mermaid
|
||
graph TD
|
||
A["需要通信?"] -->|"是"| B["通信范围?"]
|
||
|
||
B --> C["仅父子两层"]
|
||
B --> D["多层级穿透"]
|
||
B --> E["跨层级 + 兄弟之间"]
|
||
B --> F["需命令式调用子组件方法"]
|
||
|
||
C --> G["Props + Callback ✅"]
|
||
D --> H["Context ✅"]
|
||
E --> I["Zustand / Redux ✅"]
|
||
F --> J["forwardRef + useImperativeHandle ✅"]
|
||
|
||
style G fill:#4FC08D,color:#fff
|
||
style H fill:#61DAFB,color:#000
|
||
style I fill:#F5A87D,color:#000
|
||
style J fill:#A0AEC0,color:#000
|
||
```
|
||
|
||
## 关联笔记
|