Ziptax Tool Free
支持按地址、邮编或经纬度查询美国销售税率,含CLI封装与API调用说明。
构建、调试与审查 React 应用:组件、钩子、状态管理、服务端组件、表单、性能优化及测试。适用于编写或重构组件、选择状态管理方案(useState、Context、Zustand、TanStack Query、Redux)、解决组件过度重渲染或无限循环问题。
openclaw skills install @ivangdavila/react命令、参数、文件名以原文为准
用户偏好和记忆数据存储在 ~/Clawic/data/react/ 目录中(首次使用时请参考 setup.md,文件格式可参见 memory-template.md)。若旧位置存在数据(如 ~/react/ 或 ~/clawic/react/),请将其移至 ~/Clawic/data/react/。
| 情况 | 推荐方案 |
|---|---|
| 数据来自 API | 使用 TanStack Query — 避免直接使用 useState + useEffect 进行数据获取(参见 state.md) |
| 多个组件共享 UI 状态 | 使用 Zustand 切片,并通过选择器读取(参见 state.md) |
| 单个组件使用的状态 | 使用 useState,就近定义;仅当第二个消费者出现时才提升到上层 |
| 偶尔变化的全局状态(主题、语言、身份认证) | 使用 Context 并配合 memoized 值;将状态与分发逻辑拆分为两个 Context |
| 用户可书签的筛选条件、标签页、页码 | 使用 URL searchParams,而非 useState(参见 state.md) |
| 带验证的表单 | 使用 React Hook Form + Zod(非受控:输入不触发重渲染);服务端提交使用 useActionState(参见 forms.md) |
| 过滤视图中输入延迟 | 对接收的值使用 useDeferredValue;对设置的状态使用 startTransition |
| 包含数千行的列表 | 使用虚拟化(tanstack-virtual)—— DOM 节点数量是成本关键,而非数据量 |
| 组件需在身份变更时重置 | 更改其 key 属性:<Profile key={userId} /> —— 重新挂载优于手动清除状态 |
| 多步骤流程 / 向导 | 使用单一区分联合类型的字段,而非多个布尔值堆叠(参见 forms.md) |
| 程序崩溃导致整个应用空白 | 为每个路由和高风险功能添加 react-error-boundary |
| 相同组件反复提交但 props 未变 | 先检查组件本身(参见 performance.md) |
| 无限循环、状态过期、水合错误 | 参见 debug.md 中的症状链分析 |
| 代码库中存在类组件、forwardRef、PropTypes | 在新增功能前先阅读 migration.md |
| 其他情况或不确定 | 使用 colocated useState,命名导出;当第二个消费者出现时再提取 |
按需深入:debug.md 症状 → 原因链 · state.md 服务端/客户端/URL 状态 · server-components.md RSC 边界与动作 · hooks.md 效应规范、refs、自定义钩子 · performance.md 性能分析与修复优先级 · forms.md 验证、向导、文件上传 · testing.md Testing Library、MSW、异步测试 · typescript.md 类型模式 · accessibility.md 聚焦、ARIA、键盘支持 · migration.md 升级与遗留代码迁移 · setup.md 项目初始化与推荐技术栈
const total = items.reduce(...)。编写 useEffect 前先问自己:“我正在同步哪个 React 外部系统?” 无答案 → 不需要副作用(参见 hooks.md 获取完整替代表)。'use client' 标记的是边界,而非组件**。该文件导入的所有内容都会进入客户端包。应放置在交互性组件的最底层;将服务端组件作为 children 传递。key={item.id};主动更改键值是重置组件状态的官方方式。export function UserCard —— 重命名重构更安全,导入也更易搜索。strict: true 和 noUncheckedIndexedAccess**。开启后,array[0] 类型为 T | undefined —— 将原本可能运行时崩溃的情况提前变为编译错误。| 错误 | 原因 | 首步操作 |
|---|---|---|
| “过多重渲染” | 在渲染期间调用了 setState,且无条件执行 | 查找函数体中的裸 setX(...),或 onClick={fn()} 而非 onClick={fn} |
| “本次渲染的钩子数量多于上次” | 条件性钩子,或钩子上方有提前返回 | 将所有提前返回移至最后一个钩子之后(组件规则) |
| “无效的钩子调用” | 钩子不在组件或自定义钩子内 —— 或存在两个 React 版本 | 运行 npm ls react;单体仓库和链接包会无声复制 React |
| “对象不能作为 React 子节点” | 渲染 {user} 或 {date} 而非具体字段 | 渲染原始值:{user.name},{date.toISOString()} |
| “列表中的每个子节点都应有唯一 key” | 缺失或重复的 key | 使用 key={item.id}(规则 4);重复 key 表示数据本身有重复 id —— 修复数据源 |
| “水合失败” / “文本内容不匹配” | 服务端与客户端渲染出不同标记 | 参见 debug.md 中的链式排查 —— 日期/随机数/本地化、无效 HTML 嵌套、浏览器扩展 |
| “无法在渲染另一个组件时更新组件” | 在渲染期间对另一组件调用 setState | 将 setState 移入事件处理器或副作用中 |
| 压缩后的 React 错误 #NNN | 生产构建会移除提示信息 | 在 react.dev/errors/NNN 解码,然后在开发环境重现 |
export function UserCard({ user, onEdit }: UserCardProps) {
// 1. 钩子必须放在最前,且无条件
const [isOpen, setIsOpen] = useState(false)
// 2. 衍生值应在渲染中计算 —— 永远不要放在副作用中
const fullName = `${user.firstName} ${user.lastName}`
// 3. 事件处理器
const handleEdit = () => onEdit(user.id)
// 4. 提前返回必须位于所有钩子之后
if (!user.isVisible) return null
// 5. JSX
return (...)
}导出 props 接口(参见 typescript.md 了解变体 props、泛型、事件类型)。提前返回必须位于所有钩子调用之后 —— 若在钩子前返回,会导致渲染间钩子顺序不一致,引发崩溃。
数据是否来自 API?
├─ 是 → 使用 TanStack Query(非 Redux、非 Zustand、非 Context)
└─ 否 → 是否需要在刷新后保留?或跨组件共享?
└─ 是 → 使用 URL searchParams
└─ 否 → 是否跨组件共享?
├─ 是 → Zustand(频繁变化)或 Context(极少变化)
└─ 否 → 使用 useState核心默认配置(完整模式、分页、乐观更新、持久化:参见 state.md):
staleTime 默认为 0 —— 每次挂载和窗口聚焦都会重新获取数据。按资源设置 staleTime:用户可容忍的数据过期时间。保持 gcTime(默认 5 分钟) ≥ staleTime。查询键应来自统一的键工厂,确保唯一来源。useStore() 会在每次 store 变化时触发重渲染;若选择器返回新对象,需使用 useShallow。每个关注点一个 store —— 避免创建“上帝 store”,否则等同于 Redux 但缺少开发者工具。memo 包装值对象;将状态与分发逻辑拆分为两个 Context,以避免仅需 setter 的消费者被意外重渲染。server-components.md。use(promise)** 会暂停直到 Promise 解析,且可在条件中调用 —— 这是少数例外于钩子规则的情况。绝不能在客户端渲染中创建新 Promise(每次渲染都会创建新实例,导致永久挂起):应在父组件创建或缓存。ref 现在是普通属性** —— 不再需要 forwardRef。**<Context>** 会直接渲染为提供者。旧模式迁移:参见 migration.md。useActionState(action, initial) 返回 [state, formAction, pending];useFormStatus 可从任意子元素读取 pending 状态 —— 无需向提交按钮传递 prop;useOptimistic 自动渲染乐观值并在抛出时回滚。完整表单示例:参见 forms.md。操作优先级 —— 先测量再动手(工作流参见 performance.md):
| 优先级 | 技术 | 适用场景 |
|---|---|---|
| P0 | 路由级代码分割(lazy + Suspense) | 始终使用 —— 用户仅支付其访问路由的成本 |
| P0 | 并行数据加载(Promise.all、预加载) | 任意页面若有 2 个以上独立请求 —— 水瀑布效应主导加载时间 |
| P1 | 虚拟化长列表 | 数千行或数百个复杂行 —— 通过性能分析确认,DOM 节点数量是主要成本 |
| P1 | useDeferredValue / startTransition | 输入或拖拽时卡顿,因重型子树每输入一次就重渲染 |
| P2 | memo / useMemo(基于性能分析证据) | 仅在未启用 React 编译器时使用(→ 核心规则 6) |
在输出组件或审查结论前,请确认:
useEffect 是否明确关联外部系统?可推导的值是否在渲染中计算?'use client' 是否仅用于交互性叶子组件?是否有服务端导入跨越边界?button、a、label)并具备可访问名称?用户自定义变量。默认值在用户声明偏好前生效;存储于 ~/Clawic/data/react/config.yaml。
| 变量 | 类型 | 默认值 | 效果 |
|---|---|---|---|
| framework | nextjs \ | vite \ | remix |
| styling | tailwind \ | css-modules \ | styled-components |
| state_library | zustand \ | redux-toolkit \ | jotai |
| package_manager | npm \ | pnpm \ | yarn \ |
用户可记录以下偏好领域:
testing.md 示例与 scaffold 命令项目级事实(React 版本、编译器开关、技术栈偏差)应记录在 memory.md,而非配置文件 —— 因其随项目变化,而非用户偏好。
| 陷阱 | 为何失败 | 正确做法 |
|---|---|---|
{count && <X />} | 0 为假值但可渲染 —— 页面会显示字面量 0 | 改为 {count > 0 && <X />} |
key={index} 用于可排序/过滤的列表 | 状态与 DOM 绑定位置而非项目本身 —— 输入框显示错误值 | 使用 key={item.id} |
| 在副作用依赖项中使用对象/数组字面量 | 每次渲染生成新引用 → 副作用每次执行 | 依赖原始值,或上游记忆化 |
| 修改状态后立即调用 setState | 引用相同 → React 跳过重渲染 | 创建新引用:setArray([...array, item]) |
| 在副作用中获取数据但无取消机制 | 旧值响应慢,覆盖新值响应快 | 在清理函数中使用 AbortController(如下) |
| “我的副作用执行两次” → 删除 StrictMode | 双重挂载仅为开发环境且有意为之:暴露缺失清理逻辑 | 修复清理逻辑;保留 StrictMode |
| 期望错误边界捕获处理程序/异步错误 | 错误边界仅捕获渲染与生命周期错误 | 在处理程序中使用 try/catch;在突变中使用 onError |
| 水合不匹配 | Date.now()、Math.random()、本地化格式、typeof window 分支在服务端与客户端渲染不同 | 移至副作用中,或对单个元素使用 suppressHydrationWarning |
| 在渲染期间调用 setState | 渲染 → setState → 渲染,形成无限循环 | 在渲染中直接计算衍生值;setState 放入事件处理器或副作用中 |
useEffect(async () => …) | 副作用必须返回清理函数或无返回,不能返回 Promise | 在内部定义异步函数并调用 |
| 在 setState 后立即读取状态 | 状态是每渲染周期的快照 —— 变量仍持有旧值 | 使用传入的值,或使用函数式更新;副作用响应下一渲染 |
// 取消机制 —— 竞态条件修复
useEffect(() => {
const controller = new AbortController()
fetch(url, { signal: controller.signal })
.then((r) => r.json())
.then(setData)
.catch((e) => { if (e.name !== 'AbortError') setError(e) })
return () => controller.abort()
}, [url])审查 AI 生成代码时需重点检查以下模式:
| 错误 | 正确模式 |
|---|---|
| 使用 useEffect + useState 推导值 | 在渲染中直接计算(→ 核心规则 2) |
| 在 useEffect 中获取数据 | 使用 TanStack Query,或在 Suspense 下使用 promise + use() |
| 使用 Redux/Context 管理 API 数据 | 使用 TanStack Query —— 服务端状态是缓存,不是应用状态 |
| 使用默认导出 | 使用命名导出(→ 核心规则 5) |
| 对所有内容使用 useCallback/useMemo | 编译器开启时:无需手动添加;关闭时:需先经性能分析器验证(→ 核心规则 6) |
第一次使用即创建通用抽象(如 <DataTable> 仅用于一个表格) | 先写具体实现;在第二个真实消费者出现时再抽象 |
| 组件过大 | 在约 50 行 JSX 或 300 行文件处拆分(→ 核心规则 8) |
| 任意位置均无错误边界 | 在路由级别和每个高风险功能处添加 |
用 any 消除严格模式警告 | 修复类型;真正动态时使用 unknown 并进行类型缩小(参见 typescript.md) |
使用 <div onClick> 实现交互元素 | 使用原生 button/a —— 键盘与焦点支持自动获得(参见 accessibility.md) |
| 测试实现细节(状态值、mock 调用次数) | 测试用户可见的行为:角色、标签、可见输出(参见 testing.md) |
已收录 28 个 Skill