用 Claude Code 做生产级 React 开发:组件边界、Hooks、测试与审查
用 Claude Code 更安全地开发 React:组件、Hooks、表单、数据获取、测试与无障碍全流程。
Claude Code 能让 React 页面生成得很快,但生产团队真正需要的不是“更多组件”,而是边界清晰、可测试、可审查的改动。 如果只说“做一个漂亮的后台页面”,AI 很容易生成一堆看似完整、实际难维护的组件。 更稳的做法是先给 Claude Code 组件责任、状态位置、数据类型和验证命令。
本文用 React + TypeScript 说明一套可落地流程:组件边界、props、state、自定义 hook、表单、数据获取、Testing Library、无障碍、性能,以及审查 prompt。官方资料建议参考 React 文档、React Testing Library、MDN ARIA 和 Claude Code 文档。站内延伸可以看 TypeScript 技巧、测试策略、无障碍实现 与 性能优化。
先定义 Claude Code 的任务边界
props 可以理解为“父组件传给子组件的输入”,state 是“界面需要记住的值”,自定义 hook 是“把状态和副作用逻辑抽出来复用的函数”。副作用包括 API 请求、定时器、localStorage 等不只是渲染 UI 的行为。把这些术语说清楚,Claude Code 的输出会更接近团队规范。
| 需要给的信息 | 示例 | 作用 |
|---|---|---|
| 页面责任 | 用户列表、角色筛选、启停账号 | 避免一个组件做完所有事 |
| 状态位置 | 父组件、URL、custom hook、server cache | 避免 useState 到处散落 |
| 数据契约 | User 类型、API 响应、空状态 | props 和测试更具体 |
| 验证方式 | npm test、npm run build、键盘检查 | 审查时有证据 |
真实使用场景
第一个场景是内部管理后台,例如用户、订单、发票、客服工单。Claude Code 适合把页面拆成过滤器、表格、行操作、空状态和错误状态,但要明确“不要改 API 类型”“不要新增通用 Table 框架”。
第二个场景是表单,例如个人资料编辑、咨询预约、课程报名、结账信息。要让 Claude Code 保留 label、错误提示、提交中状态和失败重试逻辑。复杂表单可以继续看 React Hook Form 指南。
第三个场景是数据获取页面,例如搜索结果、通知、dashboard。server state,也就是“服务器上的状态”,不要和输入框展开状态、弹窗开关这类临时 UI state 混在一起。使用 TanStack Query 时可以参考 TanStack Query 指南,小型客户端状态可以参考 Zustand 状态管理。
第四个场景是代码审查。与其让 Claude Code 继续生成新组件,不如让它检查 diff:组件是否太大、Effect 是否必要、表单是否可访问、测试是否只靠快照、列表是否会过度重渲染。
组件结构图
UserAdminPage
-> UserFilters: 搜索和角色筛选
-> UserTable: 表格、排序提示、行操作
-> UserStatusBadge: 只负责状态显示
-> UserEditDialog: 编辑表单
-> useUsers: 数据获取、刷新、错误状态
判断边界的简单方法是:能否用一句话解释这个组件的 props。如果解释不清,通常是责任太多;如果只用一次又没有独立行为,可能暂时不需要抽组件。
可复制的类型化 React 组件
下面的例子可以放进 Vite + React + TypeScript 项目中使用。它不负责请求 API,只展示数据并把事件传回父层,因此更容易测试。
import type { FormEvent } from "react";
export type UserRole = "admin" | "editor" | "viewer";
export type User = {
id: string;
name: string;
email: string;
role: UserRole;
active: boolean;
};
type UserTableProps = {
users: User[];
selectedRole: "all" | UserRole;
onRoleChange: (role: "all" | UserRole) => void;
onToggleActive: (id: string) => void;
};
export function UserTable({
users,
selectedRole,
onRoleChange,
onToggleActive,
}: UserTableProps) {
const filteredUsers =
selectedRole === "all"
? users
: users.filter((user) => user.role === selectedRole);
function handleRoleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
onRoleChange(formData.get("role") as "all" | UserRole);
}
return (
<section aria-labelledby="user-table-title">
<h2 id="user-table-title">Team members</h2>
<form onSubmit={handleRoleSubmit} style={{ marginBottom: 12 }}>
<label htmlFor="role">Filter by role </label>
<select id="role" name="role" defaultValue={selectedRole}>
<option value="all">All</option>
<option value="admin">Admin</option>
<option value="editor">Editor</option>
<option value="viewer">Viewer</option>
</select>
<button type="submit">Apply</button>
</form>
{filteredUsers.length === 0 ? (
<p role="status">No users match this filter.</p>
) : (
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Role</th>
<th scope="col">Status</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{filteredUsers.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.role}</td>
<td>{user.active ? "Active" : "Paused"}</td>
<td>
<button type="button" onClick={() => onToggleActive(user.id)}>
{user.active ? "Pause" : "Activate"}
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</section>
);
}
自定义 hook 与测试
把请求逻辑放进 hook,可以让 UI 组件保持简单。
import { useEffect, useState } from "react";
import type { User } from "./UserTable";
type UsersState =
| { status: "loading"; users: User[]; error: null }
| { status: "success"; users: User[]; error: null }
| { status: "error"; users: User[]; error: string };
export function useUsers(endpoint: string) {
const [state, setState] = useState<UsersState>({
status: "loading",
users: [],
error: null,
});
useEffect(() => {
const controller = new AbortController();
async function loadUsers() {
setState({ status: "loading", users: [], error: null });
try {
const response = await fetch(endpoint, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const users = (await response.json()) as User[];
setState({ status: "success", users, error: null });
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") return;
setState({ status: "error", users: [], error: error instanceof Error ? error.message : "Unknown error" });
}
}
void loadUsers();
return () => controller.abort();
}, [endpoint]);
return state;
}
Testing Library 测试应从用户视角出发,优先使用 label、role 和可见文字。
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { UserTable, type User } from "./UserTable";
const users: User[] = [
{ id: "1", name: "Masa", email: "masa@example.com", role: "admin", active: true },
{ id: "2", name: "Aiko", email: "aiko@example.com", role: "viewer", active: false },
];
describe("UserTable", () => {
it("filters users and toggles active status", async () => {
const user = userEvent.setup();
const onRoleChange = vi.fn();
const onToggleActive = vi.fn();
render(<UserTable users={users} selectedRole="all" onRoleChange={onRoleChange} onToggleActive={onToggleActive} />);
await user.selectOptions(screen.getByLabelText(/filter by role/i), "viewer");
await user.click(screen.getByRole("button", { name: /apply/i }));
await user.click(screen.getByRole("button", { name: /activate/i }));
expect(onRoleChange).toHaveBeenCalledWith("viewer");
expect(onToggleActive).toHaveBeenCalledWith("2");
});
});
审查 Prompt
请审查这个 React + TypeScript diff。
重点检查组件边界、props 是否过多、useEffect 是否必要、UI state 和 server state 是否混在一起、表单 label 和错误提示、Testing Library 覆盖、以及大列表的重渲染风险。
不要主动新增库;如需建议,只作为建议列出。最后给出 npm test 和 npm run build 的验证清单。
常见坑
第一个坑是过度生成组件。UserNameText、UserEmailText 这类只出现一次、没有行为的组件通常没有价值。第二个坑是滥用 useEffect,把可计算的过滤结果也放进 Effect。第三个坑是无障碍缺失,例如只用 placeholder、图标按钮没有名称、错误只靠颜色表达。第四个坑是性能优化做成表演,到处加 memo 和 useMemo,却没有先确认瓶颈。
CTA 与验证结果
团队采用 Claude Code 时,建议把组件边界、测试命令、无障碍规则和“不要过度抽象”的原则写进 CLAUDE.md。如果你想把这套流程应用到真实 React 仓库,可以使用 ClaudeCodeLab 培训与咨询,一起整理 UI 审查、Testing Library、性能验证和咨询/报名/购买 CTA。
实际试用结果:Masa 在内容站后台 UI 改造中发现,先让 Claude Code 明确边界、状态归属和测试期望,再生成代码,后续 props 清理和无障碍返工明显减少。React 中最稳定的用法不是生成更多,而是生成更小、可验证的 diff。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
从Obsidian到CLAUDE.md的Claude Code流程:不再反复解释上下文
把 Obsidian 工作笔记整理成 CLAUDE.md 运行说明,让 Claude Code 每次都带着正确上下文开始。
Claude Code 收入 CTA 路由:从文章分流到 PDF、Gumroad 与咨询
用 Claude Code 按读者意图把文章流量分到免费 PDF、Gumroad 教材或咨询入口。
Claude Code 团队交接规则: 把审查证据、权限、回滚和收入路径一起交付
面向团队的 Claude Code 交接格式: 证据、权限、回滚、免费 PDF、Gumroad 与咨询路径都要可审查。