相信每个React开发者都遇到过这样的场景:用户在搜索框输入关键词,需要实时更新搜索结果;点击购物车按钮,要在页面头部显示商品数量;表单提交成功后,需要关闭弹窗并刷新列表...
这些看似简单的交互,背后都涉及组件间的通信问题。
今天我们就来系统梳理React中8种常用的组件通信模式,从最基础的props传递到高级的自定义Hook,让你的组件能够"优雅对话"。
Props是React中最基本的通信方式,就像父母给孩子传话一样直接:
// 父组件
function UserProfile() {
const user = { name: '张三', age: 28, avatar: 'avatar.jpg' };
return<UserCard user={user} showAge={true} />;
}
// 子组件
function UserCard({ user, showAge }) {
return (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
{showAge && <p>年龄:{user.age}</p>}
</div>
);
}
适合场景:
最佳实践:
// ✅ 好的做法:明确的prop类型
function ProductCard({
title,
price,
imageUrl,
onAddToCart,
isInStock = true
}) {
return (
<div className="product-card">
<img src={imageUrl} alt={title} />
<h3>{title}</h3>
<p className="price">¥{price}</p>
<button
onClick={() => onAddToCart({ title, price })}
disabled={!isInStock}
>
{isInStock ? '加入购物车' : '暂时缺货'}
</button>
</div>
);
}
当子组件需要告诉父组件"我做了什么事"时,回调函数就派上用场了:
// 搜索组件
function SearchBox({ onSearch }) {
const [query, setQuery] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
onSearch(query); // 告诉父组件搜索内容
};
return (
<form onSubmit={handleSubmit}>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="请输入搜索关键词"
/>
<button type="submit">搜索</button>
</form>
);
}
// 父组件
function App() {
const [searchResults, setSearchResults] = useState([]);
const handleSearch = async (query) => {
const results = await searchAPI(query);
setSearchResults(results);
};
return (
<div>
<SearchBox onSearch={handleSearch} />
<ResultsList results={searchResults} />
</div>
);
}
常见的回调函数命名:
onSubmit - 表单提交onClick - 点击事件onChange - 值变化onClose - 关闭操作onSelect - 选择操作当两个组件需要共享数据时,我们把状态"提升"到它们的共同父组件:
function ShoppingApp() {
const [cartItems, setCartItems] = useState([]);
const [selectedCategory, setSelectedCategory] = useState('all');
// 添加商品到购物车
const addToCart = (product) => {
setCartItems(prev => [...prev, product]);
};
return (
<div className="shopping-app">
{/* 头部显示购物车数量 */}
<Header cartCount={cartItems.length} />
{/* 侧边栏分类选择 */}
<Sidebar
selectedCategory={selectedCategory}
onCategoryChange={setSelectedCategory}
/>
{/* 商品列表 */}
<ProductList
category={selectedCategory}
onAddToCart={addToCart}
/>
{/* 购物车 */}
<Cart items={cartItems} />
</div>
);
}
典型场景:
当数据需要在多个层级间传递时,Context可以避免props层层传递的麻烦:
// 创建主题Context
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 在任何子组件中使用
function Header() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<header className={`header ${theme}`}>
<h1>我的网站</h1>
<button onClick={toggleTheme}>
切换到{theme === 'light' ? '深色' : '浅色'}模式
</button>
</header>
);
}
// 使用Provider包裹应用
function App() {
return (
<ThemeProvider>
<Header />
<MainContent />
<Footer />
</ThemeProvider>
);
}
适合Context的数据:
避免放入Context的数据:
有时我们需要直接调用子组件的方法,比如让输入框获得焦点:
// 自定义输入组件
const CustomInput = forwardRef(({ placeholder }, ref) => {
const inputRef = useRef();
// 暴露给父组件的方法
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
clear: () => inputRef.current.value = '',
getValue: () => inputRef.current.value
}));
return<input ref={inputRef} placeholder={placeholder} />;
});
// 父组件
function LoginForm() {
const usernameRef = useRef();
const passwordRef = useRef();
const handleSubmit = () => {
const username = usernameRef.current.getValue();
const password = passwordRef.current.getValue();
if (!username) {
usernameRef.current.focus();
return;
}
// 提交登录
login(username, password);
};
const handleReset = () => {
usernameRef.current.clear();
passwordRef.current.clear();
usernameRef.current.focus();
};
return (
<form>
<CustomInput ref={usernameRef} placeholder="用户名" />
<CustomInput ref={passwordRef} placeholder="密码" />
<button type="button" onClick={handleSubmit}>登录</button>
<button type="button" onClick={handleReset}>重置</button>
</form>
);
}
适合使用Refs的情况:
对于复杂的应用状态,全局状态库是更好的选择:
import { create } from'zustand';
// 创建用户状态store
const useUserStore = create((set, get) => ({
user: null,
isLoading: false,
// 登录
login: async (credentials) => {
set({ isLoading: true });
try {
const user = await loginAPI(credentials);
set({ user, isLoading: false });
} catch (error) {
set({ isLoading: false });
throw error;
}
},
// 登出
logout: () =>set({ user: null }),
// 更新用户信息
updateProfile: (updates) =>set((state) => ({
user: { ...state.user, ...updates }
}))
}));
// 在组件中使用
function UserProfile() {
const { user, updateProfile } = useUserStore();
const handleSave = (formData) => {
updateProfile(formData);
};
return (
<div>
<h2>用户资料</h2>
<p>姓名:{user?.name}</p>
<p>邮箱:{user?.email}</p>
<button onClick={() => handleSave({ name: '新名字' })}>
更新资料
</button>
</div>
);
}
function Header() {
const { user, logout } = useUserStore();
return (
<header>
<span>欢迎,{user?.name}</span>
<button onClick={logout}>退出登录</button>
</header>
);
}
何时使用全局状态库:
将组件通信逻辑封装成自定义Hook,提高代码复用性:
// 购物车Hook
function useShoppingCart() {
const [items, setItems] = useState([]);
const addItem = useCallback((product) => {
setItems(prev => {
const existing = prev.find(item => item.id === product.id);
if (existing) {
return prev.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...prev, { ...product, quantity: 1 }];
});
}, []);
const removeItem = useCallback((productId) => {
setItems(prev => prev.filter(item => item.id !== productId));
}, []);
const getTotalPrice = useCallback(() => {
return items.reduce((total, item) => total + item.price * item.quantity, 0);
}, [items]);
const getTotalCount = useCallback(() => {
return items.reduce((total, item) => total + item.quantity, 0);
}, [items]);
return {
items,
addItem,
removeItem,
totalPrice: getTotalPrice(),
totalCount: getTotalCount()
};
}
// 在多个组件中使用
function ProductCard({ product }) {
const { addItem } = useShoppingCart();
return (
<div>
<h3>{product.name}</h3>
<p>¥{product.price}</p>
<button onClick={() => addItem(product)}>
加入购物车
</button>
</div>
);
}
function CartSummary() {
const { totalCount, totalPrice } = useShoppingCart();
return (
<div>
<p>商品数量:{totalCount}</p>
<p>总价:¥{totalPrice}</p>
</div>
);
}
// 本地存储Hook
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setStoredValue = (newValue) => {
setValue(newValue);
localStorage.setItem(key, JSON.stringify(newValue));
};
return [value, setStoredValue];
}
// 异步数据获取Hook
function useApi(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
对于需要完全解耦的组件通信,可以使用事件总线:
// 简单的事件总线
class EventBus {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
const eventBus = new EventBus();
// 组件A:发送消息
function NotificationSender() {
const sendNotification = () => {
eventBus.emit('notification', {
message: '操作成功!',
type: 'success'
});
};
return<button onClick={sendNotification}>发送通知</button>;
}
// 组件B:接收消息
function NotificationDisplay() {
const [notifications, setNotifications] = useState([]);
useEffect(() => {
const handleNotification = (data) => {
setNotifications(prev => [...prev, { ...data, id: Date.now() }]);
};
eventBus.on('notification', handleNotification);
return() => {
eventBus.off('notification', handleNotification);
};
}, []);
return (
<div>
{notifications.map(notification => (
<div key={notification.id} className={`alert ${notification.type}`}>
{notification.message}
</div>
))}
</div>
);
}
// 场景1:简单的父子组件
function SimpleParentChild() {
const [count, setCount] = useState(0);
return (
<div>
<Counter value={count} onChange={setCount} />
<Display value={count} />
</div>
);
}
// 场景2:兄弟组件通信(状态提升)
function SiblingCommunication() {
const [selectedItem, setSelectedItem] = useState(null);
return (
<div>
<ItemList onSelect={setSelectedItem} />
<ItemDetail item={selectedItem} />
</div>
);
}
// 场景3:深层嵌套(Context)
function DeepNesting() {
return (
<UserProvider>
<Header />
<MainContent>
<Sidebar>
<UserMenu /> {/* 这里需要用户信息 */}
</Sidebar>
</MainContent>
</UserProvider>
);
}
// 场景4:复杂状态(全局状态库)
function ComplexState() {
const { user, cart, notifications } = useAppStore();
return (
<div>
<Header user={user} cartCount={cart.length} />
<NotificationList items={notifications} />
<ShoppingCart items={cart} />
</div>
);
}
场景 | 推荐方案 | 理由 |
|---|---|---|
父子组件直接通信 | Props + 回调 | 简单直接,易于理解 |
兄弟组件通信 | 状态提升 | 保持数据流清晰 |
跨多层组件的全局数据 | Context | 避免props drilling |
复杂业务状态 | 全局状态库 | 统一管理,易于调试 |
组件方法调用 | Refs | 直接控制,性能好 |
逻辑复用 | 自定义Hook | 提高代码复用性 |
完全解耦 | 事件总线 | 插件化架构 |
// ✅ 使用React.memo优化子组件
const ProductCard = React.memo(({ product, onAddToCart }) => {
return (
<div>
<h3>{product.name}</h3>
<button onClick={() => onAddToCart(product)}>
加入购物车
</button>
</div>
);
});
// ✅ 使用useCallback稳定回调函数
function ProductList({ products }) {
const [cart, setCart] = useState([]);
const handleAddToCart = useCallback((product) => {
setCart(prev => [...prev, product]);
}, []);
return (
<div>
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={handleAddToCart}
/>
))}
</div>
);
}
React组件通信的核心是选择合适的工具解决具体问题:
简单优先 - 能用props解决的不要用Context,能用Context解决的不要用全局状态库
性能考虑 - 频繁变化的数据避免放在Context中,合理使用React.memo和useCallback
可维护性 - 保持数据流向清晰,避免过度嵌套和复杂的依赖关系
团队协作 - 在团队中统一组件通信的模式,编写清晰的文档和示例
记住,没有完美的解决方案,只有最适合当前场景的方案。随着项目的发展,通信模式也可以逐步演进和优化。
最重要的是:让你的组件能够优雅地"对话",而不是"大声喊叫"!
💡 如果这篇文章对你有帮助,欢迎点赞收藏!有问题也欢迎在评论区交流讨论~