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
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 关联笔记
|