name: pitfalls-react description: “React组件模式、表单、可访问性和响应式设计。在构建React组件、处理表单或确保可访问性时使用。触发条件:React组件、useEffect、表单验证、a11y、响应式、错误边界。”
React 常见陷阱
React 开发中的常见陷阱与正确模式。
何时使用
- 构建 React 组件
- 实现表单验证
- 添加错误边界
- 确保可访问性 (a11y)
- 创建响应式布局
- 审查 React 代码
工作流程
步骤 1:检查组件模式
验证加载/错误状态和数据检查。
步骤 2:验证表单验证
确保使用 Zod 模式并正确显示错误。
步骤 3:检查可访问性
验证 ARIA 标签和键盘导航。
组件模式
// ✅ 在使用前定义辅助函数或将其导出
function formatPrice(price: number) { ... }
export default function Component() {
// ✅ 在访问前检查数据是否存在
if (!data) return <Loading />;
// ✅ useEffect 仅用于副作用
useEffect(() => {
fetchData();
}, []);
// ✅ 在交互元素上使用 data-testid
return <button data-testid="submit-btn">提交</button>;
}
// ❌ 错误:在渲染中定义函数
return <button onClick={() => {
function doSomething() { } // 不要在这里定义
doSomething();
}}>
// ✅ 使用路由器导航,而非 window
import { Link, useLocation } from 'wouter';
<Link to="/dashboard">前往</Link>
// ❌ window.location.href = '/dashboard'
错误边界
// ✅ 将主要组件包裹在错误边界中
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, info) {
logError({ error, componentStack: info.componentStack });
}
render() {
if (this.state.hasError) {
return <ErrorFallback onRetry={() => this.setState({ hasError: false })} />;
}
return this.props.children;
}
}
// ✅ 优雅降级
function Dashboard() {
const { data, error, isLoading } = useQuery(...);
if (isLoading) return <Skeleton />;
if (error) return <ErrorCard message="无法加载" onRetry={refetch} />;
if (!data) return <EmptyState />;
return <DashboardContent data={data} />;
}
表单验证
// ✅ 为所有表单使用 Zod 模式
const createStrategySchema = z.object({
name: z.string().min(1, '名称必填').max(100),
type: z.enum(['cross-exchange', 'triangular']),
minProfit: z.number().positive('必须为正数'),
});
// ✅ 将 React Hook Form 与 Zod 结合使用
const form = useForm<z.infer<typeof createStrategySchema>>({
resolver: zodResolver(createStrategySchema),
});
// ✅ 内联显示错误
{errors.name && <span className="text-red-500">{errors.name.message}</span>}
// ✅ 在验证/提交时禁用提交按钮
<button disabled={isSubmitting || !isValid}>提交</button>
响应式布局
/* ✅ 移动优先的断点 */
.container { padding: 1rem; }
@media (min-width: 768px) {
.container { padding: 2rem; }
}
/* ✅ 适合触摸的按钮尺寸 (最小 44px) */
.btn { min-height: 44px; min-width: 44px; }
/* ✅ 移动端数据表格水平滚动 */
.table-container { overflow-x: auto; }
可访问性 (a11y)
// ✅ 语义化 HTML
<nav>...</nav>
<main>...</main>
<button>点击我</button> // 不要用 <div onClick>
// ✅ ARIA 标签
<button aria-label="关闭对话框">×</button>
// ✅ 键盘导航
<button onKeyDown={(e) => e.key === 'Enter' && handleClick()}>
// ✅ 焦点指示器
button:focus { outline: 2px solid blue; outline-offset: 2px; }
快速检查清单
- [ ] 已处理加载/错误状态
- [ ] 交互元素上使用了 data-testid
- [ ] 使用路由器 Link,而非 window.location
- [ ] 辅助函数在使用前已定义
- [ ] 主要组件已设置错误边界
- [ ] 触摸目标 ≥ 44px
- [ ] 图标按钮已添加 ARIA 标签