React组件通信
React中组件间的通信是构建复杂应用的核心概念。根据组件间的关系,通信方式主要分为以下几类:
1. 父子组件通信
1.1 父传子(Props)
父组件通过props向子组件传递数据,这是最基本的通信方式。
jsx
// 父组件
function Parent() {
const [message, setMessage] = useState('Hello from parent');
return <Child message={message} />;
}
// 子组件
function Child({ message }) {
return <div>{message}</div>;
}
1.2 子传父(回调函数)
子组件通过调用父组件传递的回调函数向父组件传递数据。
jsx
// 父组件
function Parent() {
const [childData, setChildData] = useState('');
const handleChildData = (data) => {
setChildData(data);
};
return (
<div>
<p>来自子组件的数据:{childData}</p>
<Child onDataChange={handleChildData} />
</div>
);
}
// 子组件
function Child({ onDataChange }) {
const sendDataToParent = () => {
onDataChange('Hello from child');
};
return <button onClick={sendDataToParent}>发送数据给父组件</button>;
}
2. 兄弟组件通信
2.1 通过共同的父组件
兄弟组件通过共同的父组件进行状态提升来实现通信。
jsx
function Parent() {
const [sharedData, setSharedData] = useState('');
return (
<div>
<Brother1 onDataChange={setSharedData} />
<Brother2 data={sharedData} />
</div>
);
}
function Brother1({ onDataChange }) {
return (
<button onClick={() => onDataChange('来自兄弟1的数据')}>
发送数据
</button>
);
}
function Brother2({ data }) {
return <div>接收到的数据:{data}</div>;
}
3. 跨层级组件通信
3.1 Context API
适用于跨多层级的组件通信,避免props drilling问题。
jsx
// 创建Context
const UserContext = createContext();
// Provider组件
function App() {
const [user, setUser] = useState({ name: 'John', age: 30 });
return (
<UserContext.Provider value={{ user, setUser }}>
<Parent />
</UserContext.Provider>
);
}
// 深层子组件使用Context
function DeepChild() {
const { user, setUser } = useContext(UserContext);
return (
<div>
<p>用户:{user.name}</p>
<button onClick={() => setUser({...user, name: 'Jane'})}>
修改用户名
</button>
</div>
);
}
3.2 自定义Hook + Context
结合自定义Hook使用Context,提供更好的封装。
jsx
// 自定义Hook
function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within UserProvider');
}
return context;
}
// 使用
function Component() {
const { user, setUser } = useUser();
// ...
}
4. 全局状态管理
4.1 Redux
适用于大型应用的状态管理。
jsx
// Redux Store
import { createStore } from 'redux';
const initialState = {
count: 0
};
function counterReducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
}
const store = createStore(counterReducer);
// React组件
import { useSelector, useDispatch } from 'react-redux';
function Counter() {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
<div>
<p>计数:{count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>
增加
</button>
</div>
);
}
4.2 Zustand
轻量级的状态管理库。
jsx
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
function Counter() {
const { count, increment, decrement } = useStore();
return (
<div>
<p>计数:{count}</p>
<button onClick={increment}>增加</button>
<button onClick={decrement}>减少</button>
</div>
);
}
5. 事件总线(EventBus)
适用于复杂的跨组件通信场景。
jsx
// 简单的事件总线实现
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 ComponentA() {
const sendMessage = () => {
eventBus.emit('message', 'Hello from A');
};
return <button onClick={sendMessage}>发送消息</button>;
}
// 组件B
function ComponentB() {
const [message, setMessage] = useState('');
useEffect(() => {
const handleMessage = (data) => {
setMessage(data);
};
eventBus.on('message', handleMessage);
return () => {
eventBus.off('message', handleMessage);
};
}, []);
return <div>接收到的消息:{message}</div>;
}
6. Ref转发
通过ref获取子组件的实例或DOM元素。
jsx
// 函数组件使用forwardRef
const Child = forwardRef((props, ref) => {
const [value, setValue] = useState('');
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
getValue: () => value,
setValue: setValue
}));
const inputRef = useRef();
return <input ref={inputRef} value={value} onChange={(e) => setValue(e.target.value)} />;
});
// 父组件
function Parent() {
const childRef = useRef();
const handleClick = () => {
childRef.current.focus();
console.log(childRef.current.getValue());
};
return (
<div>
<Child ref={childRef} />
<button onClick={handleClick}>聚焦并获取值</button>
</div>
);
}
7. 通信方式选择指南
7.1 根据组件关系选择
组件关系 | 推荐方案 | 备选方案 |
---|---|---|
父子组件 | Props + 回调函数 | Ref |
兄弟组件 | 状态提升 | Context、事件总线 |
跨层级组件 | Context API | 全局状态管理 |
全局共享 | Redux/Zustand | Context API |
8. 最佳实践
8.1 避免Props Drilling
jsx
// ❌ Props层层传递
function App() {
const user = { name: 'John' };
return <Level1 user={user} />;
}
function Level1({ user }) {
return <Level2 user={user} />;
}
function Level2({ user }) {
return <Level3 user={user} />;
}
// ✅ 使用Context
const UserContext = createContext();
function App() {
const user = { name: 'John' };
return (
<UserContext.Provider value={user}>
<Level1 />
</UserContext.Provider>
);
}
function Level3() {
const user = useContext(UserContext);
return <div>{user.name}</div>;
}
8.3 性能优化
jsx
// 使用React.memo避免不必要的重渲染
const TodoItem = React.memo(({ todo, onToggle }) => {
return (
<div onClick={() => onToggle(todo.id)}>
{todo.text}
</div>
);
});
// 使用useCallback稳定回调函数
function TodoList({ todos }) {
const [filter, setFilter] = useState('all');
const handleToggle = useCallback((id) => {
// 处理逻辑
}, []);
return (
<div>
{todos.map(todo =>
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
)}
</div>
);
}
总结
React组件通信的核心原则:
- 就近原则:优先使用最简单、最直接的通信方式
- 单向数据流:保持数据流向清晰可预测
- 组件解耦:减少组件间的直接依赖
- 性能考虑:避免不必要的重渲染
- 可维护性:选择易于理解和维护的方案
选择合适的通信方式需要考虑应用规模、组件关系、性能要求和团队偏好等多个因素。