4.8 KiB
4.8 KiB
tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
2026-04-29 22:10 |
组件通信模式
概述
React 中父子组件之间的数据流动有多种方式。理解每种模式的适用场景和性能影响,能够避免过度设计或通信瓶颈。本文档从简单到复杂,梳理完整的通信方案谱系。
通信方向全景图
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(属性逐层透传)
问题场景
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(子 → 父)
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 跨层级通信
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}对象
状态管理库方案
// 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(命令式通信)
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>;
});
决策树
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