pr-check-frontend
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePR Review — Frontend Patterns
PR评审——前端模式
Overview
概述
This skill runs a frontend-specific self-review checklist covering React, Next.js, Vue.js, Tailwind CSS, and React Query. Use alongside and for a complete review.
pr-review-staticpr-review-human本技能会运行针对React、Next.js、Vue.js、Tailwind CSS和React Query的前端专属自我检查清单。可搭配和完成完整评审。
pr-review-staticpr-review-humanRequired Workflow
必备工作流
Follow these steps in order.
请按以下顺序执行步骤。
Step 1: Understand the diff
步骤1:理解代码差异
Run to see all changed files and understand the scope.
git diff main...HEAD运行查看所有变更文件,了解修改范围。
git diff main...HEADStep 2: Apply the Checklist
步骤2:应用检查清单
Work through each category. Flag every issue with file path and line number.
逐一检查每个分类,标记所有问题并注明文件路径和行号。
Checklist
检查清单
F1. React Hooks Rules Violations
F1. React钩子规则违反
React hooks must be called at the top level — never inside conditions, loops, or nested functions.
"Hook called inside a condition — if the condition is false on the first render, hook order changes on subsequent renders and causes state corruption."
Check:
- Are any hooks (,
useState,useEffect,useMemo,useCallback, etc.) called insideuseQuery,if, or nested functions?for - Are custom hooks always called at the top level of the component?
- Are hooks called from React function components or custom hooks only (not regular functions)?
React钩子必须在顶层调用——绝不能在条件语句、循环或嵌套函数内部调用。
"钩子在条件语句内部调用——如果首次渲染时条件不成立,后续渲染时钩子顺序会改变,导致状态损坏。"
检查项:
- 是否有任何钩子(、
useState、useEffect、useMemo、useCallback等)在useQuery、if或嵌套函数内部调用?for - 自定义钩子是否始终在组件的顶层调用?
- 钩子是否仅在React函数组件或自定义钩子中调用(而非普通函数)?
F2. Missing or Incorrect useEffect Dependencies
F2. useEffect依赖缺失或错误
useEffect"The effect readsbut it's missing from the dependency array — the effect will always run with the initial value even after userId changes." "Adding the callback directly in deps withoutuserIdcauses the effect to re-run on every render."useCallback
Check:
- Does every have a dependency array?
useEffect - Are all variables/functions used inside the effect listed in the deps array?
- Are functions in deps wrapped in to prevent infinite loops?
useCallback - Are objects/arrays in deps stable (memoized) or will they cause infinite re-renders?
useEffect"该effect读取了但未将其加入依赖数组——即使userId发生变化,effect也始终使用初始值运行。" "直接将回调函数加入依赖数组却未用userId包裹,会导致effect在每次渲染时都重新运行。"useCallback
检查项:
- 每个是否都有依赖数组?
useEffect - effect内部使用的所有变量/函数是否都已列入依赖数组?
- 依赖数组中的函数是否用包裹以避免无限循环?
useCallback - 依赖数组中的对象/数组是否稳定(已被记忆化),是否会导致无限重渲染?
F3. Unnecessary Re-renders
F3. 不必要的重渲染
Components re-rendering on every parent render when they don't need to.
Check:
- Are expensive components wrapped in where appropriate?
React.memo - Are callback props passed to memoized child components wrapped in ?
useCallback - Are derived values that are expensive to compute wrapped in ?
useMemo - Are new object/array literals created inline as props? (e.g., on every render)
style={{ margin: 0 }} - Are anonymous functions passed as props causing unnecessary re-renders?
组件在父组件每次渲染时都重新渲染,但实际上并无必要。
检查项:
- 开销较大的组件是否在合适的场景下用包裹?
React.memo - 传递给已记忆化子组件的回调属性是否用包裹?
useCallback - 计算开销较大的派生值是否用包裹?
useMemo - 是否在属性中内联创建新的对象/数组字面量?(例如,每次渲染时的)
style={{ margin: 0 }} - 是否将匿名函数作为属性传递,导致不必要的重渲染?
F4. Missing key Prop or Unstable Keys
F4. 缺失key属性或key不稳定
React uses to track list items — missing or unstable keys cause incorrect reconciliation.
key"Using array index as key means inserting an item at the top shifts all keys, causing React to re-render every item instead of just inserting one."
Check:
- Does every rendering JSX have a
.map()prop?key - Are keys unique and stable — not array indexes, not random values (e.g., )?
Math.random() - Are keys derived from a stable ID from the data (e.g., )?
item.id
React使用跟踪列表项——缺失或不稳定的key会导致错误的协调过程。
key"使用数组索引作为key意味着在顶部插入项时会改变所有key,导致React重新渲染每个项而非仅插入新项。"
检查项:
- 每个渲染JSX的是否都有
.map()属性?key - key是否唯一且稳定——不是数组索引,也不是随机值(例如)?
Math.random() - key是否来自数据中的稳定ID(例如)?
item.id
F5. Next.js SSR/SSG Gotchas
F5. Next.js SSR/SSG陷阱
Code that runs fine on the client can crash during server-side rendering.
"accessed at module level — throwswindow.localStorageduring SSR." "ReferenceError: window is not definedcalled outside a component — works in browser but fails during static generation."useRouter()
Check:
- Is ,
window,document, ornavigatoraccessed outside oflocalStorageor a client-only check?useEffect - Are browser-only APIs guarded with ?
typeof window !== 'undefined' - Are third-party libraries that access imported dynamically with
window?{ ssr: false } - Is used without wrapping in a
useSearchParams()boundary (Next.js 13+)?Suspense
在客户端运行正常的代码可能在服务器端渲染时崩溃。
"在模块级别访问——SSR期间会抛出window.localStorage错误。" "在组件外部调用ReferenceError: window is not defined——在浏览器中可用但在静态生成时失败。"useRouter()
检查项:
- 是否在或客户端专属检查之外访问
useEffect、window、document或navigator?localStorage - 仅浏览器可用的API是否用进行了防护?
typeof window !== 'undefined' - 访问的第三方库是否通过
window进行动态导入?{ ssr: false } - Next.js 13+中使用时是否未包裹在
useSearchParams()边界中?Suspense
F6. Next.js Image and Font Optimization
F6. Next.js图片和字体优化
Using instead of bypasses Next.js optimization.
<img><Image>Check:
- Are all images using Next.js component instead of
<Image>?<img> - Do components have
<Image>,width, orheightspecified?fill - Are external image domains added to
next.config.js?images.domains - Are fonts loaded via instead of a
next/fonttag in<link>?_document
使用而非会绕过Next.js的优化机制。
<img><Image>检查项:
- 所有图片是否都使用Next.js的组件而非
<Image>?<img> - 组件是否指定了
<Image>、width或height?fill - 外部图片域名是否已添加到的
next.config.js中?images.domains - 字体是否通过加载,而非在
next/font中使用_document标签?<link>
F7. React Query — Stale Data and Error Handling
F7. React Query—— stale数据和错误处理
Incorrect React Query configuration leads to stale UI or silent failures.
Check:
- Is configured appropriately? Default is
staleTime— every focus triggers a refetch.0 - Are query errors handled — is there an state shown to the user, not just
error?isLoading - Are mutations using to surface failures to the user?
onError - After a mutation, is called to refresh affected queries?
queryClient.invalidateQueries - Are dependent queries using to prevent firing before data is ready?
enabled: !!dependency - Are query keys consistent and specific — does the key include all variables the query depends on?
React Query配置错误会导致UI数据过期或静默失败。
检查项:
- 配置是否合理?默认值为
staleTime——每次聚焦都会触发重新获取。0 - 查询错误是否已处理——是否向用户展示了状态,而非仅显示
error?isLoading - 突变操作是否使用向用户展示失败信息?
onError - 突变操作后是否调用刷新受影响的查询?
queryClient.invalidateQueries - 依赖查询是否使用来防止在数据准备就绪前触发?
enabled: !!dependency - 查询key是否一致且具体——key是否包含查询依赖的所有变量?
F8. Vue.js Reactivity Pitfalls
F8. Vue.js响应式陷阱
Vue's reactivity system has known edge cases where changes are not detected.
Check:
- Are new properties added to a reactive object using /
set()re-assignment, not direct assignment (reactive()is not reactive in Vue 2)?obj.newProp = value - In Vue 3 Composition API: are values accessed with
refinside.value?<script setup> - Are properties used for derived state rather than recalculating in the template?
computed - Are dependencies stable — not causing infinite loops from mutating the watched value?
watch - Are directives paired with
v-forbindings using stable IDs?:key
Vue的响应式系统存在一些已知的边界情况,其中变更无法被检测到。
检查项:
- 向响应式对象添加新属性时是否使用/
set()重新赋值,而非直接赋值(Vue 2中reactive()不具有响应式)?obj.newProp = value - Vue 3组合式API中:内的
<script setup>值是否通过ref访问?.value - 派生状态是否使用属性而非在模板中重新计算?
computed - 的依赖是否稳定——是否因修改被监听的值而导致无限循环?
watch - 指令是否搭配使用
v-for绑定,且key为稳定ID?:key
F9. Tailwind CSS — Purge and Dynamic Classes
F9. Tailwind CSS——清除和动态类
Tailwind purges unused classes at build time — dynamically constructed class names are stripped.
"will be purged because Tailwind can't statically analyse the full class name."'text-' + color
Check:
- Are class names constructed dynamically? (e.g., ) — these will be purged in production.
`text-${color}` - Use full class names in conditionals: not
condition ? 'text-red-500' : 'text-green-500'.'text-' + color - Are custom classes in
tailwind.config.jsif they must be dynamic?safelist - Are responsive breakpoints (,
sm:,md:) applied mobile-first?lg:
Tailwind会在构建时清除未使用的类——动态构造的类名会被移除。
"会被清除,因为Tailwind无法静态分析完整的类名。"'text-' + color
检查项:
- 是否存在动态构造的类名?(例如)——这些类在生产环境中会被清除。
`text-${color}` - 在条件语句中使用完整类名:而非
condition ? 'text-red-500' : 'text-green-500'。'text-' + color - 必须动态使用的自定义类是否已加入的
tailwind.config.js?safelist - 响应式断点(、
sm:、md:)是否采用移动优先的方式应用?lg:
F10. Accessibility (a11y)
F10. 无障碍(a11y)
Check:
- Do all elements have an
<img>attribute (emptyaltfor decorative images)?alt="" - Do interactive elements (,
<div onClick>) use a proper<span onClick>or<button>instead?<a> - Are form inputs associated with a (via
<label>/htmlFororfor)?aria-label - Are modal/dialog components trapping focus correctly and dismissible with Escape?
- Are icon-only buttons labeled with ?
aria-label - Is color contrast sufficient for text against background?
检查项:
- 所有元素是否都有
<img>属性(装饰性图片使用空的alt)?alt="" - 交互式元素(、
<div onClick>)是否改用合适的<span onClick>或<button>?<a> - 表单输入是否与关联(通过
<label>/htmlFor或for)?aria-label - 模态框/对话框组件是否正确捕获焦点,且可通过Escape键关闭?
- 仅含图标的按钮是否用标注?
aria-label - 文本与背景的颜色对比度是否足够?
F11. TypeScript — Component Props Typing
F11. TypeScript——组件属性类型
Check:
- Are component props typed with an interface or type — not ?
any - Are optional props marked with and required props enforced?
? - Are event handler types correct: not
React.ChangeEvent<HTMLInputElement>?any - Are props typed as
children?React.ReactNode - Are props typed with
refwhen usingReact.Ref<ElementType>?forwardRef
检查项:
- 组件属性是否使用接口或类型定义——而非?
any - 可选属性是否标记了,必填属性是否被强制要求?
? - 事件处理函数类型是否正确:使用而非
React.ChangeEvent<HTMLInputElement>?any - 属性是否被定义为
children类型?React.ReactNode - 使用时,
forwardRef属性是否用ref定义类型?React.Ref<ElementType>
F12. State Management — Minimal and Co-located State
F12. 状态管理——最小化且就近放置的状态
Check:
- Is state lifted only as high as necessary? State shared by two siblings belongs in their parent, not a global store.
- Is server state (data from API) managed via React Query / SWR rather than manually in ?
useState - Is derived state computed (via or
useMemo) rather than stored in a separatecomputed?useState - Are multiple related calls that always update together combined into one object?
useState
检查项:
- 状态是否仅提升到必要的高度?两个子组件共享的状态应放在父组件中,而非全局存储。
- 服务端状态(来自API的数据)是否通过React Query/SWR管理,而非手动用处理?
useState - 派生状态是否通过或
useMemo计算,而非存储在单独的computed中?useState - 多个总是一起更新的相关调用是否合并为一个对象?
useState
F13. Environment Variables
F13. 环境变量
Check:
- Are client-side env vars prefixed with (Next.js) or
NEXT_PUBLIC_(Vite)?VITE_ - Are secret/server-only env vars never prefixed with — this exposes them to the browser bundle?
NEXT_PUBLIC_ - Are all new env vars added to with a placeholder value?
.env.example
检查项:
- 客户端环境变量是否带有(Next.js)或
NEXT_PUBLIC_(Vite)前缀?VITE_ - 机密/仅服务端环境变量是否从未添加前缀——这会将其暴露给浏览器打包文件?
NEXT_PUBLIC_ - 所有新增环境变量是否都已添加到中并带有占位值?
.env.example
F14. Bundle Size Awareness
F14. 包体积意识
Check:
- Are large libraries imported in full when only a part is needed? (e.g., vs
import _ from 'lodash')import debounce from 'lodash/debounce' - Are heavy components (charts, editors, maps) lazy-loaded with ?
dynamic(() => import(...), { ssr: false }) - Are new dependencies checked for bundle size impact (bundlephobia.com)?
检查项:
- 是否在仅需部分功能时导入了完整的大型库?(例如vs
import _ from 'lodash')import debounce from 'lodash/debounce' - 重型组件(图表、编辑器、地图)是否通过懒加载?
dynamic(() => import(...), { ssr: false }) - 是否已检查新增依赖对包体积的影响(可使用bundlephobia.com)?
F15. Error Boundaries
F15. 错误边界
Check:
- Are async components or data-fetching components wrapped in an Error Boundary?
- In Next.js App Router, is there an for route segments that can fail?
error.tsx - Are loading states handled with /
loading.tsxrather than conditionally rendering null?Suspense
检查项:
- 异步组件或数据获取组件是否用错误边界包裹?
- Next.js App Router中,可能失败的路由段是否有对应的?
error.tsx - 加载状态是否通过/
loading.tsx处理,而非条件渲染null?Suspense
F16. Forms and Validation
F16. 表单与验证
Check:
- Are forms using a form library (React Hook Form, Formik, VeeValidate) rather than manual per field?
useState - Is validation both client-side (UX) and server-side (security)?
- Are form submissions debounced or disabled while in-flight to prevent double submissions?
- Are error messages shown next to the relevant field, not just a generic toast?
检查项:
- 表单是否使用表单库(React Hook Form、Formik、VeeValidate)而非为每个字段手动使用?
useState - 是否同时进行客户端验证(用户体验)和服务端验证(安全性)?
- 表单提交是否已防抖或在请求进行中禁用,以防止重复提交?
- 错误信息是否显示在相关字段旁,而非仅显示通用提示框?
Step 3: Report Findings
步骤3:报告发现
For each issue found, output:
[F3] components/ProductList.tsx:42
Severity: Medium
Issue: Anonymous callback passed as onClick prop to memoized child — defeats memo.
Fix: Wrap the callback in useCallback with appropriate dependencies.
[F9] components/Badge.tsx:8
Severity: High
Issue: Class name constructed as `'bg-' + color` — will be purged in production build.
Fix: Use full class names: condition ? 'bg-red-500' : 'bg-green-500'.对于每个发现的问题,输出如下格式:
[F3] components/ProductList.tsx:42
Severity: Medium
Issue: Anonymous callback passed as onClick prop to memoized child — defeats memo.
Fix: Wrap the callback in useCallback with appropriate dependencies.
[F9] components/Badge.tsx:8
Severity: High
Issue: Class name constructed as `'bg-' + color` — will be purged in production build.
Fix: Use full class names: condition ? 'bg-red-500' : 'bg-green-500'.Step 4: Summary
步骤4:总结
End with:
- Total issues by severity (High / Medium / Low)
- Files most affected
- Verdict: ready for review / needs fixes before submitting
结尾需包含:
- 按严重程度分类的问题总数(高/中/低)
- 受影响最严重的文件
- 结论:可提交评审 / 需要修复后再提交",