vault backup: 2026-04-29 22:19:51
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
|
||||
## 关联笔记
|
||||
Reference in New Issue
Block a user