SKILL.md
readonly只读
name
typescript-react-reviewer
description
TypeScript + React 19 应用程序的专家代码审查员。在审查 React 代码、识别反模式、评估状态管理或评估代码可维护性时使用。触发条件:代码审查请求、PR 审查、React 架构评估、识别代码异味、TypeScript 类型安全检查、useEffect 滥用检测、状态管理审查。
TypeScript + React 19 代码审查专家
具有 React 19 新特性、TypeScript 最佳实践、状态管理模式和常见反模式深入知识的专家代码审查员。
审查优先级级别
🚫 严重(阻止合并)
这些问题会导致错误、内存泄漏或架构问题:
| 问题 | 为什么严重 |
|---|---|
使用 useEffect 处理派生状态 |
额外的渲染周期、同步错误 |
useEffect 中缺少清理 |
内存泄漏 |
直接修改状态(.push()、.splice()) |
静默更新失败 |
| 条件调用 Hook | 违反 Hook 规则 |
动态列表中使用 key={index} |
重新排序时状态损坏 |
无正当理由使用 any 类型 |
绕过类型安全 |
在与 <form> 相同的组件中使用 useFormStatus |
始终返回 false(React 19 错误) |
在渲染中使用 use() 创建 Promise |
无限循环 |
⚠️ 高优先级
| 问题 | 影响 |
|---|---|
| 依赖数组不完整 | 闭包过期、更新缺失 |
Props 类型为 any |
运行时错误 |
无正当理由使用 useMemo/useCallback |
不必要的复杂性 |
| 缺少错误边界 | 错误用户体验差 |
受控输入初始化为 undefined |
React 警告 |
📝 架构/风格
| 问题 | 建议 |
|---|---|
| 组件超过 300 行 | 拆分为更小的组件 |
| Props 钻取超过 2-3 层 | 使用组合或上下文 |
| 状态远离使用位置 | 将状态就近放置 |
自定义 Hook 没有 use 前缀 |
遵循命名约定 |
快速检测模式
useEffect 滥用(最常见的反模式)
// ❌ 错误:在 useEffect 中处理派生状态
const [firstName, setFirstName] = useState('');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ✅ 正确:在渲染期间计算
const fullName = firstName + ' ' + lastName;
// ❌ 错误:在 useEffect 中处理事件逻辑
useEffect(() => {
if (product.isInCart) showNotification('Added!');
}, [product]);
// ✅ 正确:在事件处理函数中处理逻辑
function handleAddToCart() {
addToCart(product);
showNotification('Added!');
}
React 19 Hook 错误
// ❌ 错误:在表单组件中使用 useFormStatus(始终返回 false)
function Form() {
const { pending } = useFormStatus();
return <form action={submit}><button disabled={pending}>Send</button></form>;
}
// ✅ 正确:在子组件中使用 useFormStatus
function SubmitButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>Send</button>;
}
function Form() {
return <form action={submit}><SubmitButton /></form>;
}
// ❌ 错误:在渲染中创建 Promise(无限循环)
function Component() {
const data = use(fetch('/api/data')); // 每次渲染都会创建新的 Promise!
}
// ✅ 正确:从 props 或 state 获取 Promise
function Component({ dataPromise }: { dataPromise: Promise<Data> }) {
const data = use(dataPromise);
}
状态修改检测
// ❌ 错误:直接修改(不会触发重新渲染)
items.push(newItem);
setItems(items);
arr[i] = newValue;
setArr(arr);
// ✅ 正确:不可变更新
setItems([...items, newItem]);
setArr(arr.map((x, idx) => idx === i ? newValue : x));
TypeScript 危险信号
// ❌ 需要捕获的危险信号
const data: any = response; // 不安全的 any
const items = arr[10]; // 缺少 undefined 检查
const App: React.FC<Props> = () => {}; // 不推荐的模式
// ✅ 推荐模式
const data: ResponseType = response;
const items = arr[10]; // 配合 noUncheckedIndexedAccess
const App = ({ prop }: Props) => {}; // 显式 props
审查工作流程
- 首先扫描严重问题 - 检查“严重(阻止合并)”部分中的模式
- 检查 React 19 的使用 - 参见 react19-patterns.md 了解新 API 模式
- 评估状态管理 - 状态是否就近放置?服务端状态与客户端状态是否分离?
- 评估 TypeScript 安全性 - 泛型组件、可辨识联合、严格配置
- 检查可维护性 - 组件大小、Hook 设计、文件夹结构
参考文档
有关详细模式和示例:
- react19-patterns.md - React 19 新 Hooks(useActionState、useOptimistic、use)、服务端/客户端组件边界
- antipatterns.md - 全面的反模式目录及修复方法
- checklist.md - 完整代码审查清单,用于全面审查
状态管理快速指南
| 数据类型 | 解决方案 |
|---|---|
| 服务端/异步数据 | TanStack Query(切勿复制到本地状态) |
| 简单的全局 UI 状态 | Zustand(约 1KB,无需 Provider) |
| 细粒度派生状态 | Jotai(约 2.4KB) |
| 组件本地状态 | useState/useReducer |
| 表单状态 | React 19 useActionState |
TanStack Query 反模式
// ❌ 切勿将服务端数据复制到本地状态
const { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos });
const [todos, setTodos] = useState([]);
useEffect(() => setTodos(data), [data]);
// ✅ Query 就是数据源
const { data: todos } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos });
TypeScript 配置建议
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true
}
}
noUncheckedIndexedAccess 至关重要 - 它可以捕获 arr[i] 返回 undefined 的情况。
立即标记的危险信号
审查时,立即标记以下内容:
| 模式 | 问题 | 修复 |
|---|---|---|
eslint-disable react-hooks/exhaustive-deps |
隐藏闭包过期错误 | 重构逻辑 |
| 在组件内部定义组件 | 每次渲染都会重新挂载 | 移到外部 |
输入使用 useState(undefined) |
非受控警告 | 使用空字符串 |
带泛型的 React.FC |
泛型推断失败 | 使用显式 props |
应用代码中的桶文件(index.ts) |
包体积膨胀、循环依赖 | 直接导入 |






