typescript-react-reviewer by dotneet/claude-code-marketplace
npx skills add https://github.com/dotneet/claude-code-marketplace --skill typescript-react-reviewer具备 React 19 新特性、TypeScript 最佳实践、状态管理模式和常见反模式深度知识的专家级代码审查员。
这些问题会导致错误、内存泄漏或架构问题:
| 问题 | 为何关键 |
|---|---|
useEffect 用于派生状态 | 额外的渲染周期,同步错误 |
useEffect 中缺少清理函数 | 内存泄漏 |
直接状态突变(.push()、.splice()) | 静默更新失败 |
| 条件性 Hook 调用 | 违反 Hook 规则 |
动态列表中使用 key={index} | 重新排序时状态损坏 |
无正当理由使用 any 类型 | 绕过类型安全 |
| 在与 相同的组件中使用 |
| 问题 | 影响 |
|---|---|
| 不完整的依赖数组 | 过时的闭包,缺少更新 |
属性类型定义为 any | 运行时错误 |
无正当理由使用 useMemo/useCallback | 不必要的复杂性 |
| 缺少错误边界 | 错误用户体验差 |
使用 undefined 初始化的受控输入 | React 警告 |
| 问题 | 建议 |
|---|---|
| 组件超过 300 行 | 拆分为更小的组件 |
| 属性透传超过 2-3 层 | 使用组合或上下文 |
| 状态远离使用位置 | 状态就近放置 |
自定义 Hook 没有 use 前缀 | 遵循命名约定 |
// ❌ 错误:在 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!');
}
// ❌ 错误:在表单组件中使用 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!
}
// ✅ 正确:从属性或状态获取 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));
// ❌ 需要警惕的危险信号
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) => {}; // 显式属性
详细模式和示例:
| 数据类型 | 解决方案 |
|---|---|
| 服务器/异步数据 | TanStack Query(切勿复制到本地状态) |
| 简单的全局 UI 状态 | Zustand(约 1KB,无需 Provider) |
| 细粒度派生状态 | Jotai(约 2.4KB) |
| 组件本地状态 | useState/useReducer |
| 表单状态 | React 19 useActionState |
// ❌ 切勿将服务器数据复制到本地状态
const { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos });
const [todos, setTodos] = useState([]);
useEffect(() => setTodos(data), [data]);
// ✅ Query 本身就是真相来源
const { data: todos } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos });
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true
}
}
noUncheckedIndexedAccess 至关重要 - 它能捕获 arr[i] 返回 undefined 的情况。
审查时,立即标记以下情况:
| 模式 | 问题 | 修复方案 |
|---|---|---|
eslint-disable react-hooks/exhaustive-deps | 隐藏过时闭包错误 | 重构逻辑 |
| 组件内部定义组件 | 每次渲染都重新挂载 | 移到外部 |
输入框使用 useState(undefined) | 不受控警告 | 使用空字符串 |
带泛型的 React.FC | 泛型推断失效 | 使用显式属性 |
应用代码中的桶文件(index.ts) | 打包体积膨胀,循环依赖 | 直接导入 |
每周安装量
3.6K
代码仓库
首次出现
2026年1月21日
安全审计
Gen Agent Trust HubPassSocketPassSnykPass
安装于
claude-code2.9K
opencode2.0K
gemini-cli2.0K
codex2.0K
github-copilot2.0K
kimi-cli1.8K
Expert code reviewer with deep knowledge of React 19's new features, TypeScript best practices, state management patterns, and common anti-patterns.
These issues cause bugs, memory leaks, or architectural problems:
| Issue | Why It's Critical |
|---|---|
useEffect for derived state | Extra render cycle, sync bugs |
Missing cleanup in useEffect | Memory leaks |
Direct state mutation (.push(), .splice()) | Silent update failures |
| Conditional hook calls | Breaks Rules of Hooks |
| in dynamic lists |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
<form>useFormStatus| 始终返回 false(React 19 错误) |
在渲染内部创建 Promise 并与 use() 一起使用 | 无限循环 |
key={index}| State corruption on reorder |
any type without justification | Type safety bypass |
useFormStatus in same component as <form> | Always returns false (React 19 bug) |
Promise created inside render with use() | Infinite loop |
| Issue | Impact |
|---|---|
| Incomplete dependency arrays | Stale closures, missing updates |
Props typed as any | Runtime errors |
Unjustified useMemo/useCallback | Unnecessary complexity |
| Missing Error Boundaries | Poor error UX |
Controlled input initialized with undefined | React warning |
| Issue | Recommendation |
|---|---|
| Component > 300 lines | Split into smaller components |
| Prop drilling > 2-3 levels | Use composition or context |
| State far from usage | Colocate state |
Custom hooks without use prefix | Follow naming convention |
// ❌ WRONG: Derived state in useEffect
const [firstName, setFirstName] = useState('');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ✅ CORRECT: Compute during render
const fullName = firstName + ' ' + lastName;
// ❌ WRONG: Event logic in useEffect
useEffect(() => {
if (product.isInCart) showNotification('Added!');
}, [product]);
// ✅ CORRECT: Logic in event handler
function handleAddToCart() {
addToCart(product);
showNotification('Added!');
}
// ❌ WRONG: useFormStatus in form component (always returns false)
function Form() {
const { pending } = useFormStatus();
return <form action={submit}><button disabled={pending}>Send</button></form>;
}
// ✅ CORRECT: useFormStatus in child component
function SubmitButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>Send</button>;
}
function Form() {
return <form action={submit}><SubmitButton /></form>;
}
// ❌ WRONG: Promise created in render (infinite loop)
function Component() {
const data = use(fetch('/api/data')); // New promise every render!
}
// ✅ CORRECT: Promise from props or state
function Component({ dataPromise }: { dataPromise: Promise<Data> }) {
const data = use(dataPromise);
}
// ❌ WRONG: Mutations (no re-render)
items.push(newItem);
setItems(items);
arr[i] = newValue;
setArr(arr);
// ✅ CORRECT: Immutable updates
setItems([...items, newItem]);
setArr(arr.map((x, idx) => idx === i ? newValue : x));
// ❌ Red flags to catch
const data: any = response; // Unsafe any
const items = arr[10]; // Missing undefined check
const App: React.FC<Props> = () => {}; // Discouraged pattern
// ✅ Preferred patterns
const data: ResponseType = response;
const items = arr[10]; // with noUncheckedIndexedAccess
const App = ({ prop }: Props) => {}; // Explicit props
For detailed patterns and examples:
| Data Type | Solution |
|---|---|
| Server/async data | TanStack Query (never copy to local state) |
| Simple global UI state | Zustand (~1KB, no Provider) |
| Fine-grained derived state | Jotai (~2.4KB) |
| Component-local state | useState/useReducer |
| Form state | React 19 useActionState |
// ❌ NEVER copy server data to local state
const { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos });
const [todos, setTodos] = useState([]);
useEffect(() => setTodos(data), [data]);
// ✅ Query IS the source of truth
const { data: todos } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos });
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true
}
}
noUncheckedIndexedAccess is critical - it catches arr[i] returning undefined.
When reviewing, flag these immediately:
| Pattern | Problem | Fix |
|---|---|---|
eslint-disable react-hooks/exhaustive-deps | Hides stale closure bugs | Refactor logic |
| Component defined inside component | Remounts every render | Move outside |
useState(undefined) for inputs | Uncontrolled warning | Use empty string |
React.FC with generics | Generic inference breaks | Use explicit props |
Barrel files (index.ts) in app code | Bundle bloat, circular deps | Direct imports |
Weekly Installs
3.6K
Repository
First Seen
Jan 21, 2026
Security Audits
Installed on
claude-code2.9K
opencode2.0K
gemini-cli2.0K
codex2.0K
github-copilot2.0K
kimi-cli1.8K
99,500 周安装