📚 概述
组件设计模式是 React 开发中的核心概念。理解受控/非受控组件以及容器组件模式,能帮助你写出更清晰、更可维护的代码。
1️⃣ 受控组件(Controlled Components)
受控组件是指表单数据由 React 状态管理的组件。
核心特点
- ✅ 单一数据源:表单值存储在 state 中
- ✅ 实时验证:可以在输入时进行验证
- ✅ 强制格式:可以控制输入格式
- ✅ 条件禁用:可以根据条件禁用提交按钮
代码示例:表单验证
ini
import { useState } from 'react';
function ControlledForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const [errors, setErrors] = useState({});
const validateField = (name, value) => {
switch (name) {
case 'email':
return /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(value)
? '' : '请输入有效的邮箱地址';
case 'password':
return value.length >= 8
? '' : '密码至少需要 8 个字符';
default:
return '';
}
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
// 实时验证
const error = validateField(name, value);
setErrors(prev => ({ ...prev, [name]: error }));
};
const handleSubmit = (e) => {
e.preventDefault();
// 提交前验证所有字段
const newErrors = {};
Object.keys(formData).forEach(key => {
const error = validateField(key, formData[key]);
if (error) newErrors[key] = error;
});
if (Object.keys(newErrors).length === 0) {
console.log('提交数据:', formData);
} else {
setErrors(newErrors);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>用户名:</label>
<input
name="username"
value={formData.username}
onChange={handleChange}
/>
</div>
<div>
<label>邮箱:</label>
<input
name="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <span style={{color: 'red'}}>{errors.email}</span>}
</div>
<div>
<label>密码:</label>
<input
name="password"
type="password"
value={formData.password}
onChange={handleChange}
/>
{errors.password && <span style={{color: 'red'}}>{errors.password}</span>}
</div>
<button
type="submit"
disabled={Object.values(errors).some(e => e) || !formData.username}
>
提交
</button>
</form>
);
}
2️⃣ 非受控组件(Uncontrolled Components)
非受控组件是指表单数据由 DOM 自身管理的组件,使用 ref 来访问表单值。
适用场景
- 📁 文件输入(
<input type="file" />) - 🔌 第三方库集成(不兼容 React 状态管理)
- ⚡ 简单表单(不需要实时验证)
- 📊 性能优化(避免频繁重渲染)
代码示例:使用 useRef
javascript
import { useRef } from 'react';
function UncontrolledForm() {
const formRef = useRef(null);
const fileInputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
// 通过 ref 获取表单数据
const formData = new FormData(formRef.current);
const data = Object.fromEntries(formData.entries());
console.log('表单数据:', data);
// 访问文件输入
const file = fileInputRef.current.files[0];
if (file) {
console.log('选中的文件:', file.name);
}
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<div>
<label>用户名:</label>
<input name="username" defaultValue="" />
</div>
<div>
<label>邮箱:</label>
<input name="email" type="email" defaultValue="" />
</div>
<div>
<label>上传文件:</label>
<input
ref={fileInputRef}
name="file"
type="file"
/>
</div>
<button type="submit">提交</button>
</form>
);
}
3️⃣ 容器组件模式(Container Component Pattern)
容器组件负责数据获取和业务逻辑,展示组件负责 UI 渲染。
核心思想
- 🧠 容器组件:处理数据、状态、业务逻辑
- 🎨 展示组件:只负责接收 props 并渲染 UI
- 🔄 关注点分离:逻辑与视图解耦
代码示例:用户列表
javascript
// UserList.jsx - 展示组件(纯 UI)
function UserList({ users, loading, error, onRefresh }) {
if (loading) return <div>加载中...</div>;
if (error) return <div>错误:{error}</div>;
return (
<div>
<button onClick={onRefresh}>刷新</button>
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
);
}
// UserListContainer.jsx - 容器组件(数据逻辑)
import { useState, useEffect } from 'react';
import UserList from './UserList';
function UserListContainer() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchUsers = async () => {
try {
setLoading(true);
const response = await fetch('/api/users');
const data = await response.json();
setUsers(data);
setError(null);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchUsers();
}, []);
return (
<UserList
users={users}
loading={loading}
error={error}
onRefresh={fetchUsers}
/>
);
}
export default UserListContainer;
4️⃣ 现代替代方案:自定义 Hooks
随着 Hooks 的普及,自定义 Hooks 成为容器组件的现代替代方案。
代码示例:useUsers Hook
javascript
// hooks/useUsers.js
import { useState, useEffect } from 'react';
export function useUsers() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchUsers = async () => {
try {
setLoading(true);
const response = await fetch('/api/users');
const data = await response.json();
setUsers(data);
setError(null);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchUsers();
}, []);
return { users, loading, error, refetch: fetchUsers };
}
// 使用 Hook 的组件
import { useUsers } from './hooks/useUsers';
function UserPage() {
const { users, loading, error, refetch } = useUsers();
if (loading) return <div>加载中...</div>;
if (error) return <div>错误:{error}</div>;
return (
<div>
<button onClick={refetch}>刷新</button>
<ul>
{users.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
</div>
);
}
💡 模式对比
| 特性 | 受控组件 | 非受控组件 | 容器组件 | 自定义 Hooks |
|---|---|---|---|---|
| 数据源 | React state | DOM | React state | React state |
| 实时验证 | ✅ | ❌ | ✅ | ✅ |
| 性能 | 中等 | 高 | 中等 | 高 |
| 代码复用 | 中 | 低 | 高 | 非常高 |
| 推荐场景 | 表单验证 | 文件输入/简单表单 | 数据获取 | 数据获取(现代) |
⚠️ 选择建议
- 需要实时验证 → 受控组件
- 集成第三方库 → 非受控组件
- 复杂数据逻辑 → 自定义 Hooks(优先)或容器组件
- 简单表单 → 非受控组件(性能更好)