关于 code-review
code-review 是面向 React、TypeScript 和 Tailwind 前端项目的代码审查技能,适用于检查组件、功能或页面实现。它按安全、性能、代码质量、类型、无障碍和测试等维度组织问题,并给出分级反馈与修复建议。
主要功能
- 01检查前端安全风险与输入校验
- 02评估 React 性能和组件架构
- 03审查 TypeScript 类型安全
- 04检查无障碍与响应式实现
- 05按优先级组织问题和建议
适用场景
- 01提交前审查主要功能改动
- 02检查组件的安全与性能问题
- 03定位页面中的无障碍和类型缺陷
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ smallnest/ langgraphgo --skill code-review安装后在 Claude Code 中输入/code-review确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
使用说明.mdcode-review/resources/FEEDBACK-GUIDELINES.mdREVIEW-CHECKLIST.mdreferences/accessibility-guide.mdfrontend-security.mdreact-patterns.mdsecurity-checklist.md详细介绍
前端代码审查
这项技能可为现代前端应用提供全面且可用于生产环境的代码审查,并针对 React/TypeScript/Tailwind 技术栈给出可操作的反馈。
目的
将前端代码审查从人工检查转变为系统化分析,涵盖:
- 前端安全 - XSS、CSRF、敏感数据泄露、身份验证问题
- React 性能 - 重新渲染、记忆化、打包体积、延迟加载
- 代码质量 - 可读性、可维护性、React 最佳实践
- 组件架构 - 分层架构、关注点分离、可复用性
- 类型安全 - TypeScript 使用、类型正确性、运行时验证
- 无障碍 - WCAG 合规性、键盘导航、屏幕阅读器
- 响应式设计 - 移动优先、断点、Tailwind 模式
- SEO 与元数据 - 元标签、语义化 HTML、性能指标
- 测试 - 组件测试、钩子测试、边缘情况
- 状态管理 - Zustand/Context 模式、React Query 使用
何时使用这项技能
在以下情况下使用这项技能:
- 用户要求进行代码审查或提供反馈
- 用户提到:“review”“check”“feedback”“quality”“security”
- 生成组件或功能后
- 用户询问性能或无障碍方面的问题
- 提交重大更改之前
- 示例:
- “审查这个组件”
- “这段 React 代码是否经过优化?”
- “能检查一下无障碍问题吗?”
- “我该如何改进?”
- “审查我的功能实现”
审查流程
步骤 1:了解上下文
审查之前,先收集上下文:
-
代码类型:
- React 组件(UI、表单、列表等)
- 自定义 Hook(业务逻辑)
- 实用函数(辅助函数、转换)
- API 集成(React Query、fetch)
- Store/状态管理(Zustand、Context)
- 样式(Tailwind、CSS-in-JS)
-
审查范围:
- 单个组件/Hook
- 整个功能(多个文件)
- 页面/路由实现
- 共享实用工具
-
优先级:
- 安全关键型(身份验证、支付表单)
- 性能关键型(大型列表、复杂计算)
- 面向用户(无障碍、UX)
- 内部使用(实用工具、辅助函数)
步骤 2:初步扫描
快速扫描明显问题:
严重问题(🚨 严重):
- XSS 漏洞(dangerouslySetInnerHTML)
- CSRF 漏洞(缺少令牌)
- 敏感数据泄露(令牌存储在 localStorage 中)
- 身份验证绕过
- 硬编码的密钥/API 密钥
高优先级(⚠️ 高):
- 性能瓶颈(不必要的重新渲染、未使用记忆化)
- 内存泄漏(useEffect 中缺少清理)
- 错误处理缺失
- 违反无障碍规范(缺少 ARIA 标签、键盘支持)
- 缺少输入验证
中优先级(⚡ 中):
- 代码重复
- 组件/变量名称不清晰
- 缺少加载/错误状态
- TypeScript 使用不当(any 类型)
- Tailwind 使用不一致
低优先级(💡 低):
- 代码风格不一致
- 复杂逻辑缺少注释
- 细微的优化机会
- 文档缺失
步骤 3:深入分析
针对所有维度进行系统化审查:
3.1 前端安全审查
检查常见的前端漏洞:
// ❌ BAD: XSS vulnerability
function UserComment({ comment }: { comment: string }) {
return <div dangerouslySetInnerHTML={{ __html: comment }} />;
}
// ✅ GOOD: Sanitized HTML
import DOMPurify from 'dompurify';
function UserComment({ comment }: { comment: string }) {
return <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(comment) }} />;
}
// ✅ BETTER: No HTML, just text
function UserComment({ comment }: { comment: string }) {
return <div>{comment}</div>;
}
// ❌ BAD: Token in localStorage (XSS vulnerable)
localStorage.setItem('token', response.token);
// ✅ GOOD: HttpOnly cookie (set by server)
// Or use secure session management library
// ❌ BAD: Hardcoded API key
const API_KEY = "pk_live_abc123xyz";
// ✅ GOOD: Environment variable
const API_KEY = process.env.NEXT_PUBLIC_API_KEY;
// ❌ BAD: No CSRF protection
async function transferMoney(to: string, amount: number) {
await fetch('/api/transfer', {
method: 'POST',
body: JSON.stringify({ to, amount })
});
}
// ✅ GOOD: Include CSRF token
async function transferMoney(to: string, amount: number) {
const csrfToken = getCsrfToken();
await fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ to, amount })
});
}
前端安全检查清单:
- 不使用包含用户输入的
dangerouslySetInnerHTML - 不在 localStorage 中存储敏感令牌
- 不硬编码 API 密钥或机密信息
- 对会更改状态的操作启用 CSRF 保护
- 验证所有表单输入
- 对用户生成的内容进行输出清理
- 强制使用 HTTPS(检查生产环境)
- console.log 中不包含敏感数据
- 对受保护的路由进行适当的身份验证检查
- 安全处理密码输入(敏感字段不启用自动填充)
3.2 React 性能审查
识别瓶颈和优化机会:
// ❌ BAD: Unnecessary re-renders
function ProductList({ products }: { products: Product[] }) {
const sortedProducts = products.sort((a, b) => b.price - a.price);
// Re-sorts on every render!
return (
<div>
{sortedProducts.map(p => (
<ProductCard key={p.id} product={p} onUpdate={() => updateProduct(p.id)} />
))}
</div>
);
}
// ✅ GOOD: Memoized sorting and callbacks
function ProductList({ products }: { products: Product[] }) {
const sortedProducts = useMemo(
() => [...products].sort((a, b) => b.price - a.price),
[products]
);
const handleUpdate = useCallback((id: string) => {
updateProduct(id);
}, []);
return (
<div>
{sortedProducts.map(p => (
<ProductCard key={p.id} product={p} onUpdate={() => handleUpdate(p.id)} />
))}
</div>
);
}
// ❌ BAD: No memoization for expensive child
function ExpensiveChild({ data }: { data: Data }) {
// Complex rendering logic
return <div>{/* ... */}</div>;
}
// ✅ GOOD: Memoized component
const ExpensiveChild = memo(function ExpensiveChild({ data }: { data: Data }) {
// Complex rendering logic
return <div>{/* ... */}</div>;
});
// ❌ BAD: Memory leak - no cleanup
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// No cleanup!
}, []);
return <div>{count}</div>;
}
// ✅ GOOD: Proper cleanup
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>{count}</div>;
}
// ❌ BAD: No lazy loading for large components
import HeavyChart from './HeavyChart';
import HeavyEditor from './HeavyEditor';
// ✅ GOOD: Lazy loading
const HeavyChart = lazy(() => import('./HeavyChart'));
const HeavyEditor = lazy(() => import('./HeavyEditor'));
function Dashboard() {
return (
<Suspense fallback={<Spinner />}>
<HeavyChart />
<HeavyEditor />
</Suspense>
);
}
// ❌ BAD: Inline object/function props
<Child
config={{ theme: 'dark' }}
onUpdate={() => doSomething()}
/>
// ✅ GOOD: Memoized props
const config = useMemo(() => ({ theme: 'dark' }), []);
const handleUpdate = useCallback(() => doSomething(), []);
<Child config={config} onUpdate={handleUpdate} />
React 性能检查清单:
- 对开销较大的计算进行记忆化(useMemo)
- 为子组件使用记忆化回调(useCallback)
- 对开销较大的子组件使用 memo()
- 在 useEffect 中进行适当清理(间隔任务、订阅)
- 对大型组件进行延迟加载
- 对路由进行代码拆分
- 对长列表进行虚拟化(react-window)
- 对频繁触发的事件进行防抖(搜索、滚动)
- 优化图像(next/image、延迟加载)
- 不要将内联对象/函数作为 props
- 正确使用 key(唯一 ID,而非索引)
- 避免深层 prop 逐层传递(使用 Context/Zustand)
3.3 组件架构审查
检查关注点是否正确分离:
// ❌ BAD: Everything in one component
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// API call in component
useEffect(() => {
setLoading(true);
fetch('/api/user')
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
// Business logic in component
const fullName = user ? `${user.firstName} ${user.lastName}` : '';
const isAdult = user?.age >= 18;
// Validation in component
const validateEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{fullName}</div>;
}
// ✅ GOOD: Layered architecture
// api/userApi.ts (Data Access Layer)
export const userApi = {
getUser: async (): Promise<User> => {
const response = await fetch('/api/user');
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
}
};
// hooks/useUser.ts (Business Logic Layer)
export function useUser() {
const { data: user, isLoading, error } = useQuery({
queryKey: ['user'],
queryFn: userApi.getUser
});
const fullName = user ? `${user.firstName} ${user.lastName}` : '';
const isAdult = user?.age >= 18;
return { user, fullName, isAdult, isLoading, error };
}
// utils/validation.ts (Business Logic Layer)
export const emailSchema = z.string().email();
export function validateEmail(email: string): boolean {
return emailSchema.safeParse(email).success;
}
// components/UserProfile.tsx (Presentation Layer)
export function UserProfile() {
const { fullName, isLoading, error } = useUser();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return <div className="text-lg font-semibold">{fullName}</div>;
}
架构检查清单:
- 分层架构(表现层、业务逻辑层、数据访问层)
- 不要直接在组件中调用 API
- 不要在组件中编写业务逻辑
- 使用 Hooks 封装可复用逻辑
- 使用 Zustand/Context 管理全局状态
- 使用 React Query 管理服务端状态
- 不存在循环依赖
- 遵循单一职责原则
- 组件小巧且专注(<200 行)
- 正确声明 prop 类型(TypeScript 接口)
3.4 类型安全审查
检查 TypeScript 的使用是否规范:
// ❌ BAD: Using 'any'
function processData(data: any) {
return data.value;
}
// ✅ GOOD: Proper types
interface ProcessData {
value: string;
count: number;
}
function processData(data: ProcessData): string {
return data.value;
}
// ❌ BAD: Non-null assertion without justification
function getUser(users: User[], id: string) {
return users.find(u => u.id === id)!.name; // Dangerous!
}
// ✅ GOOD: Proper null handling
function getUser(users: User[], id: string): string | null {
return users.find(u => u.id === id)?.name ?? null;
}
// ❌ BAD: No runtime validation
function LoginForm() {
const handleSubmit = (data: unknown) => {
// Assuming data structure without validation
login(data);
};
}
// ✅ GOOD: Runtime validation with Zod
import { z } from 'zod';
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8)
});
type LoginData = z.infer<typeof loginSchema>;
function LoginForm() {
const handleSubmit = (data: unknown) => {
try {
const validatedData = loginSchema.parse(data);
login(validatedData); // Type-safe
} catch (err) {
// Handle validation errors
}
};
}
// ❌ BAD: Implicit any in event handlers
<input onChange={(e) => setValue(e.target.value)} />
// 'e' is implicitly 'any' in some configs
// ✅ GOOD: Explicit types
<input onChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value)} />
// ✅ BETTER: Typed handler
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
<input onChange={handleChange} />
类型安全检查清单:
- 不使用 'any' 类型(确有必要的情况除外)
- 不使用没有正当理由的非空断言(!)
- 为 props 正确定义接口/类型
- 使用 Zod 对外部数据进行运行时验证
- 为可辨识联合类型添加类型守卫
- 为事件处理程序显式声明类型
- 在 tsconfig.json 中启用严格模式
- 不存在隐式 any
- 在 Hooks 中正确使用泛型
3.5 无障碍审查
检查是否符合 WCAG 2.1 AA:
// ❌ BAD: No keyboard support, no ARIA labels
<div onClick={handleClick}>
<input type="text" placeholder="Name" />
<div className="text-red-500">Error message</div>
</div>
// ✅ GOOD: Semantic HTML, ARIA labels, keyboard support
<form onSubmit={handleSubmit}>
<label htmlFor="name" className="block text-sm font-medium">
Name
</label>
<input
id="name"
type="text"
aria-label="Enter your name"
aria-describedby="name-error"
aria-invalid={hasError}
className="mt-1 block w-full"
/>
{hasError && (
<p id="name-error" role="alert" className="text-red-500 text-sm">
Error message
</p>
)}
<button type="submit" className="mt-4 px-4 py-2 bg-blue-500">
Submit
</button>
</form>
// ❌ BAD: No alt text, poor color contrast
<div className="bg-gray-200 text-gray-300">
<img src="/icon.png" />
Click here
</div>
// ✅ GOOD: Alt text, proper contrast (WCAG AA)
<div className="bg-gray-900 text-white">
<img src="/icon.png" alt="Settings icon" />
<button className="text-lg font-medium">
Open Settings
</button>
</div>
// ❌ BAD: Custom select without keyboard navigation
function CustomSelect({ options }) {
const [open, setOpen] = useState(false);
return (
<div onClick={() => setOpen(!open)}>
{open && options.map(opt => (
<div onClick={() => selectOption(opt)}>{opt}</div>
))}
</div>
);
}
// ✅ GOOD: Accessible custom select
function CustomSelect({ options }: { options: string[] }) {
const [open, setOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowDown') {
setSelectedIndex(i => Math.min(i + 1, options.length - 1));
} else if (e.key === 'ArrowUp') {
setSelectedIndex(i => Math.max(i - 1, 0));
} else if (e.key === 'Enter') {
selectOption(options[selectedIndex]);
}
};
return (
<div
role="combobox"
aria-expanded={open}
aria-haspopup="listbox"
tabIndex={0}
onKeyDown={handleKeyDown}
onClick={() => setOpen(!open)}
>
{open && (
<ul role="listbox">
{options.map((opt, index) => (
<li
key={opt}
role="option"
aria-selected={index === selectedIndex}
onClick={() => selectOption(opt)}
>
{opt}
</li>
))}
</ul>
)}
</div>
);
}
无障碍检查清单:
- 使用语义化 HTML 元素(button、nav、main 等)
- 为交互元素添加 ARIA 标签
- 支持键盘导航(Tab、Enter、方向键)
- 管理焦点(确保焦点状态清晰可见)
- 颜色对比度符合 WCAG AA(文本为 4.5:1)
- 为所有图像添加替代文本
- 正确关联表单标签(htmlFor)
- 错误消息使用 role="alert"
- 不存在键盘陷阱
- 为主要内容添加跳过链接
- 标题顺序合理(h1、h2、h3)
- 为交互元素设置正确的角色
3.6 响应式设计审查
检查 Tailwind/响应式设计模式:
// ❌ BAD: Fixed widths, no responsive classes
<div className="w-800 h-600">
<img src="/hero.jpg" className="w-full" />
</div>
// ✅ GOOD: Responsive Tailwind classes
<div className="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<img
src="/hero.jpg"
alt="Hero"
className="w-full h-auto object-cover md:h-96 lg:h-[500px]"
/>
</div>
// ❌ BAD: No mobile considerations
<div className="flex gap-8">
<Sidebar />
<MainContent />
</div>
// ✅ GOOD: Mobile-first responsive layout
<div className="flex flex-col lg:flex-row gap-4 lg:gap-8">
<aside className="w-full lg:w-64">
<Sidebar />
</aside>
<main className="flex-1">
<MainContent />
</main>
</div>
// ❌ BAD: Fixed font sizes
<h1 className="text-32">Title</h1>
// ✅ GOOD: Responsive typography
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold">
Title
</h1>
响应式设计检查清单:
- 采用移动优先的方法(基础样式针对移动端)
- 使用正确的断点(sm、md、lg、xl、2xl)
- 使用灵活的布局(flex、grid)
- 使用响应式图像(w-full h-auto、object-fit)
- 使用响应式排版(响应式文字大小)
- 点击区域适合触控(最小 44x44px)
- 移动端不出现水平滚动
- 在多种屏幕尺寸下进行测试
3.7 代码质量审查
评估代码的可读性和可维护性:
// ❌ BAD: Unclear names, deeply nested
function p(d) {
if (d) {
if (d.u) {
if (d.u.n) {
if (d.u.n.length > 0) {
return d.u.n;
}
}
}
}
return 'Anonymous';
}
// ✅ GOOD: Clear names, early returns
function getUserName(data: UserData | null): string {
if (!data?.user?.name) return 'Anonymous';
if (data.user.name.length === 0) return 'Anonymous';
return data.user.name;
}
// ✅ BETTER: Optional chaining
function getUserName(data: UserData | null): string {
return data?.user?.name || 'Anonymous';
}
// ❌ BAD: Magic numbers and strings
if (user.age > 18 && status === 'active') {
grantAccess();
}
// ✅ GOOD: Named constants
const MINIMUM_AGE = 18;
const USER_STATUS = {
ACTIVE: 'active',
INACTIVE: 'inactive'
} as const;
if (user.age > MINIMUM_AGE && status === USER_STATUS.ACTIVE) {
grantAccess();
}
// ❌ BAD: Long component with multiple responsibilities
function UserDashboard() {
// 300 lines of code handling:
// - User data fetching
// - Analytics tracking
// - Notification handling
// - UI rendering
}
// ✅ GOOD: Split into focused components
function UserDashboard() {
return (
<div>
<UserProfile />
<UserAnalytics />
<UserNotifications />
</div>
);
}
代码质量检查清单:
- 使用清晰且有描述性的名称(getUserName,而非 gn)
- 不使用魔法数字或字符串(使用常量)
- 使用提前返回,避免深层嵌套
- 函数/组件保持简短(<100 行)
- 遵循单一职责原则
- 遵循 DRY(不要重复自己)
- 不保留注释掉的代码
- 生产代码中不包含 console.log
- 代码风格保持一致
- 正确处理错误
3.8 React Hooks 最佳实践
// ❌ BAD: Missing dependencies
useEffect(() => {
fetchData(userId, filter);
}, []); // userId and filter are missing!
// ✅ GOOD: All dependencies included
useEffect(() => {
fetchData(userId, filter);
}, [userId, filter]);
// ❌ BAD: Object dependency (recreated every render)
const options = { sort: 'asc', limit: 10 };
useEffect(() => {
fetchData(options);
}, [options]); // Will run every render!
// ✅ GOOD: Memoized object
const options = useMemo(() => ({
sort: 'asc',
limit: 10
}), []);
useEffect(() => {
fetchData(options);
}, [options]);
// ❌ BAD: Stale closure
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1); // Uses stale count!
}, 1000);
return () => clearInterval(interval);
}, []); // Empty deps, count is stale
return <div>{count}</div>;
}
// ✅ GOOD: Functional update
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1); // Uses current count
}, 1000);
return () => clearInterval(interval);
}, []); // Empty deps OK now
return <div>{count}</div>;
}
第 4 步:提供结构化反馈
清晰地组织审查结果:
## Code Review: [Component/Feature Name]
### Summary
[Brief overall assessment: Excellent/Good/Needs Improvement/Critical Issues]
### Critical Issues 🚨
1. **[Issue Title]** - [file:line]
- **Problem**: [Description]
- **Impact**: [Security/Performance/Accessibility]
- **Fix**:
```typescript
// Suggested fix
高优先级 ⚠️
[相同格式]
中优先级 ⚡
[相同格式]
低优先级 💡
[相同格式]
优点 ✅
- [突出积极方面]
- [采用的良好实践]
建议
- [可执行的改进措施]
- [可执行的改进措施]
总体评分
- 安全性:8/10
- 性能:7/10
- 代码质量:9/10
- 无障碍:6/10
- 类型安全:8/10
总体:7.6/10
## Integration with Other Skills
### With feature-builder
- Review complete features (UI + business logic + API)
- Ensure layered architecture is properly implemented
### With react-component-generator
- Review generated components
- Check template usage and customization
### With ui-analyzer
- Review UI code generated from design screenshots
- Verify responsive design implementation
## Best Practices
1. **Be Constructive**: Always provide actionable fixes with code examples
2. **Prioritize**: Focus on critical issues (security, accessibility) first
3. **Explain Why**: Help user understand the reasoning and impact
4. **Show Before/After**: Provide clear code examples
5. **Be Specific**: Reference exact lines and files
6. **Balance**: Highlight what's good too
7. **Be Practical**: Consider project constraints and deadlines
8. **Educate**: Explain React/frontend concepts when needed
## Severity Levels
- 🚨 **CRITICAL**: Security vulnerabilities (XSS, CSRF), data exposure, crashes
- ⚠️ **HIGH**: Performance issues (memory leaks), accessibility violations, broken UX
- ⚡ **MEDIUM**: Code quality, maintainability, missing TypeScript types
- 💡 **LOW**: Code style, documentation, minor optimizations
## Reference Files
- `references/frontend-security.md` - Frontend security best practices
- `references/react-patterns.md` - React patterns and anti-patterns
- `references/accessibility-guide.md` - WCAG compliance guide
This skill enables thorough, professional frontend code reviews that improve code quality, security, and user experience!
评论
支持 Markdown