Appearance
组件通信大全
组件通信是 React 开发中的重要环节,不同组件之间需要传递数据和事件。本章节将介绍 React 中常见的组件通信方式。
9.1 父传子:props
父组件通过 props 向子组件传递数据,这是最基本的组件通信方式。
基本使用
jsx
// 父组件
function Parent() {
const message = 'Hello from Parent';
return (
<div>
<Child message={message} />
</div>
);
}
// 子组件
function Child({ message }) {
return <p>{message}</p>;
}传递复杂数据
jsx
// 父组件
function Parent() {
const user = {
name: '张三',
age: 20,
email: 'zhangsan@example.com'
};
return (
<div>
<Child user={user} />
</div>
);
}
// 子组件
function Child({ user }) {
return (
<div>
<h1>{user.name}</h1>
<p>年龄:{user.age}</p>
<p>邮箱:{user.email}</p>
</div>
);
}传递函数
jsx
// 父组件
function Parent() {
const [count, setCount] = useState(0);
function handleIncrement() {
setCount(count + 1);
}
return (
<div>
<p>计数:{count}</p>
<Child onIncrement={handleIncrement} />
</div>
);
}
// 子组件
function Child({ onIncrement }) {
return (
<button onClick={onIncrement}>
增加计数
</button>
);
}9.2 子传父:回调函数
子组件通过调用父组件传递的回调函数来向父组件传递数据。
基本使用
jsx
// 父组件
function Parent() {
const [message, setMessage] = useState('');
function handleMessageChange(newMessage) {
setMessage(newMessage);
}
return (
<div>
<p>子组件消息:{message}</p>
<Child onMessageChange={handleMessageChange} />
</div>
);
}
// 子组件
function Child({ onMessageChange }) {
const [inputValue, setInputValue] = useState('');
function handleChange(e) {
setInputValue(e.target.value);
onMessageChange(e.target.value);
}
return (
<input
type="text"
value={inputValue}
onChange={handleChange}
placeholder="请输入消息"
/>
);
}传递多个参数
jsx
// 父组件
function Parent() {
const [user, setUser] = useState({ name: '', age: 0 });
function handleUserChange(name, age) {
setUser({ name, age });
}
return (
<div>
<p>姓名:{user.name}</p>
<p>年龄:{user.age}</p>
<Child onUserChange={handleUserChange} />
</div>
);
}
// 子组件
function Child({ onUserChange }) {
const [name, setName] = useState('');
const [age, setAge] = useState('');
function handleSubmit(e) {
e.preventDefault();
onUserChange(name, parseInt(age));
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
placeholder="姓名"
/>
<input
type="number"
value={age}
onChange={e => setAge(e.target.value)}
placeholder="年龄"
/>
<button type="submit">提交</button>
</form>
);
}9.3 跨组件通信:useContext + createContext
对于多层级的组件通信,使用 Context API 可以避免 props 层层传递的问题。
创建 Context
jsx
// src/contexts/ThemeContext.jsx
import { createContext, useState, useContext } from 'react';
// 创建 Context
const ThemeContext = createContext();
// 提供者组件
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 自定义 Hook
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}使用 Context
jsx
// App.jsx
import { ThemeProvider } from './contexts/ThemeContext';
import Header from './components/Header';
import Main from './components/Main';
function App() {
return (
<ThemeProvider>
<Header />
<Main />
</ThemeProvider>
);
}
// Header.jsx
import { useTheme } from '../contexts/ThemeContext';
function Header() {
const { theme, setTheme } = useTheme();
return (
<header style={{ backgroundColor: theme === 'light' ? '#fff' : '#333' }}>
<h1>Header</h1>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</header>
);
}
// Main.jsx
import { useTheme } from '../contexts/ThemeContext';
function Main() {
const { theme } = useTheme();
return (
<main style={{ backgroundColor: theme === 'light' ? '#f0f0f0' : '#444', color: theme === 'light' ? '#333' : '#fff' }}>
<h2>Main Content</h2>
<p>当前主题:{theme}</p>
</main>
);
}9.4 祖孙组件通信:useContext 或 中间组件转发
使用 useContext
jsx
// 祖父组件
function Grandparent() {
const [data, setData] = useState('Hello from Grandparent');
return (
<DataProvider value={{ data, setData }}>
<Parent />
</DataProvider>
);
}
// 父组件(中间组件)
function Parent() {
return <Child />;
}
// 孙组件
function Child() {
const { data, setData } = useData();
return (
<div>
<p>{data}</p>
<button onClick={() => setData('Updated by Child')}>
更新数据
</button>
</div>
);
}中间组件转发
jsx
// 祖父组件
function Grandparent() {
const [data, setData] = useState('Hello from Grandparent');
return <Parent data={data} onDataChange={setData} />;
}
// 父组件(中间组件)
function Parent({ data, onDataChange }) {
return <Child data={data} onDataChange={onDataChange} />;
}
// 孙组件
function Child({ data, onDataChange }) {
return (
<div>
<p>{data}</p>
<button onClick={() => onDataChange('Updated by Child')}>
更新数据
</button>
</div>
);
}9.5 全局状态通信:useReducer + useContext
对于复杂的全局状态管理,可以使用 useReducer 结合 useContext。
创建状态管理
jsx
// src/contexts/CountContext.jsx
import { createContext, useReducer, useContext } from 'react';
// 初始状态
const initialState = {
count: 0
};
// Reducer 函数
function countReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return { count: 0 };
default:
return state;
}
}
// 创建 Context
const CountContext = createContext();
// 提供者组件
export function CountProvider({ children }) {
const [state, dispatch] = useReducer(countReducer, initialState);
return (
<CountContext.Provider value={{ state, dispatch }}>
{children}
</CountContext.Provider>
);
}
// 自定义 Hook
export function useCount() {
const context = useContext(CountContext);
if (!context) {
throw new Error('useCount must be used within a CountProvider');
}
return context;
}使用全局状态
jsx
// App.jsx
import { CountProvider } from './contexts/CountContext';
import Counter from './components/Counter';
import Display from './components/Display';
function App() {
return (
<CountProvider>
<Counter />
<Display />
</CountProvider>
);
}
// Counter.jsx
import { useCount } from '../contexts/CountContext';
function Counter() {
const { dispatch } = useCount();
return (
<div>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>增加</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>减少</button>
<button onClick={() => dispatch({ type: 'RESET' })}>重置</button>
</div>
);
}
// Display.jsx
import { useCount } from '../contexts/CountContext';
function Display() {
const { state } = useCount();
return <p>计数:{state.count}</p>;
}9.6 组件通信场景总结
| 通信方式 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| Props | 父子组件通信 | 简单直接 | 不适合多层级通信 |
| 回调函数 | 子父组件通信 | 简单直接 | 不适合多层级通信 |
| Context API | 跨组件通信 | 避免 props 层层传递 | 不适合频繁更新的状态 |
| useReducer + Context | 复杂全局状态 | 集中管理状态逻辑 | 增加代码复杂度 |
| 状态管理库(Redux、Zustand) | 大型应用 | 强大的状态管理能力 | 学习成本高 |
新手选型指南
- 简单父子通信:使用 Props 和回调函数
- 跨组件通信:使用 Context API
- 复杂状态管理:使用 useReducer + Context 或状态管理库
- 小型应用:优先使用 React 内置的状态管理方案
- 大型应用:考虑使用专业的状态管理库
实战练习
练习1:TodoList 组件通信
jsx
// App.jsx
import { useState } from 'react';
import TodoForm from './components/TodoForm';
import TodoList from './components/TodoList';
function App() {
const [todos, setTodos] = useState([]);
function addTodo(todo) {
setTodos(prevTodos => [...prevTodos, todo]);
}
function deleteTodo(index) {
setTodos(prevTodos => prevTodos.filter((_, i) => i !== index));
}
return (
<div>
<TodoForm onAddTodo={addTodo} />
<TodoList todos={todos} onDeleteTodo={deleteTodo} />
</div>
);
}
// TodoForm.jsx
import { useState } from 'react';
function TodoForm({ onAddTodo }) {
const [inputValue, setInputValue] = useState('');
function handleSubmit(e) {
e.preventDefault();
if (inputValue) {
onAddTodo(inputValue);
setInputValue('');
}
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={inputValue}
onChange={e => setInputValue(e.target.value)}
placeholder="请输入任务"
/>
<button type="submit">添加</button>
</form>
);
}
// TodoList.jsx
function TodoList({ todos, onDeleteTodo }) {
return (
<ul>
{todos.map((todo, index) => (
<li key={index}>
{todo}
<button onClick={() => onDeleteTodo(index)}>删除</button>
</li>
))}
</ul>
);
}练习2:Context API 应用
jsx
// src/contexts/UserContext.jsx
import { createContext, useState, useContext } from 'react';
const UserContext = createContext();
export function UserProvider({ children }) {
const [user, setUser] = useState(null);
function login(username, password) {
// 模拟登录
setUser({ username, id: 1 });
}
function logout() {
setUser(null);
}
return (
<UserContext.Provider value={{ user, login, logout }}>
{children}
</UserContext.Provider>
);
}
export function useUser() {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUser must be used within a UserProvider');
}
return context;
}
// App.jsx
import { UserProvider } from './contexts/UserContext';
import LoginForm from './components/LoginForm';
import UserProfile from './components/UserProfile';
function App() {
return (
<UserProvider>
<LoginForm />
<UserProfile />
</UserProvider>
);
}
// LoginForm.jsx
import { useState } from 'react';
import { useUser } from '../contexts/UserContext';
function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const { user, login } = useUser();
function handleSubmit(e) {
e.preventDefault();
login(username, password);
}
if (user) {
return <p>已登录:{user.username}</p>;
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={username}
onChange={e => setUsername(e.target.value)}
placeholder="用户名"
/>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="密码"
/>
<button type="submit">登录</button>
</form>
);
}
// UserProfile.jsx
import { useUser } from '../contexts/UserContext';
function UserProfile() {
const { user, logout } = useUser();
if (!user) {
return <p>请先登录</p>;
}
return (
<div>
<h1>用户资料</h1>
<p>用户名:{user.username}</p>
<button onClick={logout}>退出登录</button>
</div>
);
}通过本章节的学习,你已经掌握了 React 中常见的组件通信方式。在实际开发中,根据不同的场景选择合适的通信方式,可以使代码更加清晰和易于维护。
