React 19完整工程化:架构设计、服务端组件、Hooks、Zustand、TanStack Query、表单处理、性能优化、测试与生产部署。
React是一项面向实际任务的技能,主要用于Production- 级 React 工程. 这种技能会改变您构建 React 应用程序的方式—— 从组件架构到部署.;构建 React 组件、 页面或 fea。
从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;
若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。
面向生产环境的 React 工程实践。该技能将彻底改变你构建 React 应用的方式——从组件架构到部署全流程。
编写代码前,请先明确以下决策:
| 决策项 | 可选方案 | 默认值 |
|---|---|---|
| 渲染方式 | SPA / SSR / 静态生成 / 混合渲染 | SSR(Next.js) |
| 服务端状态 | TanStack Query / SWR / use() | TanStack Query |
| 客户端状态 | useState / Zustand / Jotai | 若需跨组件共享则使用 Zustand |
| 样式方案 | Tailwind / CSS Modules / styled | Tailwind |
| 表单处理 | React Hook Form + Zod / 原生表单 | RHF + Zod |
规则:服务端状态(API 数据)与客户端状态(UI 状态)是完全不同的两类状态,严禁混用。
// ✅ 正确模式
export function UserCard({ user, onEdit }: UserCardProps) {
// 1. Hook 声明置于最前(始终如此)
const [isOpen, setIsOpen] = useState(false)
// 2. 衍生状态(禁止使用 useEffect 计算)
const fullName = `${user.firstName} ${user.lastName}`
// 3. 事件处理器
const handleEdit = useCallback(() => onEdit(user.id), [onEdit, user.id])
// 4. 提前返回
if (!user) return null
// 5. JSX(最多 50 行)
return (...)
}
| 规则 | 原因 |
|---|---|
| 仅使用具名导出(named exports) | 保障重构安全性,提升 IDE 支持度 |
| 导出 Props 接口 | 便于复用,具备类型文档能力 |
| JSX 最多 50 行 | 超出则拆分为子组件 |
| 单文件最多 300 行 | 按职责拆分为多个组件 |
| Hook 声明统一置于顶部 | 符合 React 规则,逻辑更可预测 |
是否来自 API?
├─ 是 → 使用 TanStack Query(禁止使用 Redux 或 Zustand)
└─ 否 → 是否在多个组件间共享?
├─ 是 → 使用 Zustand(简单场景)或 Context(变更频率极低时)
└─ 否 → 使用 useState
// 查询键工厂 —— 防止拼写错误
export const userKeys = {
all: ['users'] as const,
detail: (id: string) => [...userKeys.all, id] as const,
}
export function useUser(id: string) {
return useQuery({
queryKey: userKeys.detail(id),
queryFn: () => fetchUser(id),
staleTime: 5 * 60 * 1000, // 5 分钟
})
}
// 轻量级 Store,每个 Store 关注单一职责 export const useUIStore = create()((set) => ({ sidebarOpen: true, toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })), })) // 务必使用 selector —— 避免不必要的重渲染 const isOpen = useUIStore((s) => s.sidebarOpen)
// 服务端组件 —— 在服务端执行,零 JS 下发至客户端
async function ProductList() {
const products = await db.products.findMany() // 直接访问数据库
return | 服务端组件 | 客户端组件 |
|---|---|
| 支持 async/await ✅ | 支持 useState ✅ |
| 支持直接数据库访问 ✅ | 支持 onClick ✅ |
| 不增加打包体积 | 增加打包体积 |
| 不支持 useState ❌ | 不支持 async ❌ |
// 在渲染中读取 Promise(配合 Suspense)
function Comments({ promise }: { promise: Promise }) {
const comments = use(promise) // 暂停渲染直至 Promise 解析完成
return {comments.map(c => - {c.text}
)}
}
'use client'
async function submitAction(prev: State, formData: FormData) {
'use server'
// ... 服务端逻辑
return { success: true }
}
function Form() {
const [state, action, pending] = useActionState(submitAction, {})
return (
)
}
| 优先级 | 技术手段 | 影响程度 |
|---|---|---|
| P0 | 基于路由的代码分割 | 🔴 高 |
| P0 | 图片优化(next/image) | 🔴 高 |
| P1 | 长列表虚拟化(tanstack-virtual) | 🟡 中 |
| P1 | 防抖昂贵操作 | 🟡 中 |
| P2 | 对昂贵组件使用 React.memo | 🟢 低–中 |
| P2 | 对昂贵计算使用 useMemo | 🟢 低–中 |
React Compiler(React 19+):自动 memo 化。请移除所有手动的 memo/useMemo/useCallback。
// ❌ 当 count 为 0 时渲染字符串 "0"
{count && }
// ✅ 显式布尔判断
{count > 0 && }
// ❌ 直接修改原数组 —— React 无法检测到变化 array.push(item) setArray(array) // ✅ 创建新引用 setArray([...array, item])
// ❌ 每次渲染都生成新 key —— 导致组件被销毁重建- // ✅ 使用稳定 key
// ❌ useEffect 不支持异步函数
useEffect(async () => { ... }, [])
// ✅ 在内部定义并调用异步函数
useEffect(() => {
async function load() { ... }
load()
}, [])
// ❌ 缺少清理逻辑 —— 可能导致内存泄漏
useEffect(() => {
const sub = subscribe()
}, [])
// ✅ 返回清理函数
useEffect(() => {
const sub = subscribe()
return () => sub.unsubscribe()
}, [])
// ❌ 将对象放入依赖数组 —— 每次渲染都会触发
useEffect(() => { ... }, [{ id: 1 }])
// ✅ 提取基础类型或使用 memoize
useEffect(() => { ... }, [id])
// ❌ 串行请求 —— 性能差 const users = await fetchUsers() const orders = await fetchOrders() // ✅ 并行请求 const [users, orders] = await Promise.all([fetchUsers(), fetchOrders()])
// ❌ 竞态条件 —— 无中止机制
useEffect(() => {
fetch(url).then(setData)
}, [url])
// ✅ 使用 AbortController
useEffect(() => {
const controller = new AbortController()
fetch(url, { signal: controller.signal }).then(setData)
return () => controller.abort()
}, [url])
AI 助手在 React 开发中常犯的典型错误:
| 错误 | 正确模式 |
|---|---|
| 用 useEffect 计算衍生状态 | 内联计算:const x = a + b |
| 用 Redux 管理 API 数据 | 服务端状态统一使用 TanStack Query |
| 使用默认导出(default export) | 仅使用具名导出:export function X |
| 动态列表中用 index 作 key | 使用稳定 ID:key={item.id} |
| 在 useEffect 中发起数据请求 | 应使用 TanStack Query 或 loader 模式 |
| 巨型组件(500+ 行) | JSX 超过 50 行或文件超 300 行即拆分 |
| 未设置错误边界(Error Boundary) | 在应用层、功能模块层、组件层均需添加 |
| 忽略 TypeScript 严格模式 | 启用 strict: true 并修复全部类型错误 |
| Hook | 用途 |
|---|---|
| useState | 本地状态 |
| useEffect | 副作用(订阅、DOM 操作等) |
| useCallback | 保持函数引用稳定 |
| useMemo | 缓存昂贵计算结果 |
| useRef | 可变引用,用于 DOM 访问等 |
| use() | 读取 Promise 或 Context(React 19) |
| useActionState | 表单动作状态(React 19) |
| useOptimistic | 乐观 UI(React 19) |
src/ ├── app/ # 路由(Next.js) ├── features/ # 功能模块 │ └── auth/ │ ├── components/ # 功能相关组件 │ ├── hooks/ # 功能相关 Hook │ ├── api/ # API 请求封装 │ └── index.ts # 公共导出入口 ├── shared/ # 跨功能复用 │ ├── components/ui/ # Button、Input 等通用组件 │ └── hooks/ # useDebounce 等通用 Hook └── providers/ # Context Provider
首次配置请参阅 setup.md。项目跟踪使用 memory-template.md。
export function X,禁止 export default;保障安全重构能力。const total = items.reduce(...);useEffect 仅用于副作用。item.id,禁止使用 index。any,禁用隐式 null/undefined;在编译期捕获类型错误。如用户确认,可通过 clawhub install 安装:
clawhub star reactclawhub sync