ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Continue Checks 实战:用 React Best Practices 规则实现 PR 自动审查与修复

Continue Checks 实战:用 React Best Practices 规则实现 PR 自动审查与修复 Continue Checks 实战用 React Best Practices 规则实现 PR 自动审查与修复【免费下载链接】continueopen-source coding agent项目地址: https://gitcode.com/GitHub_Trending/co/continuereact-best-practices.md 是 Continue 仓库中一个典型的 Check 定义文件它把一套按影响程度分级、带错误/正确代码对照的 React 与 Next.js 性能优化规则包装成一个可以跑在 Pull Request 上的“审查代理”。本文以该文档为主体完整讲解这份规则的八大类检查项与示例代码并结合仓库中 Check 的执行机制源码说明这条规则如何从一份 Markdown 变成真实的 PR 审查与自动修复流程。读完后你既能把这份规则清单当作团队 React 代码审查的标准使用也能理解 Continue 的 Checks 体系如何驱动这类规则的落地。一、这份 Check 文件的定位与角色定义.continue/checks/目录存放的是 Continue 的“检查项”定义与同目录的 anti-slop.md、security-audit.md、stale-comments.md 等并列。每个 Check 文件以 YAML frontmatter 声明元信息其余部分就是交给审查代理的系统提示词--- name: React Best Practices description: React Best Practices ---该文件开篇明确定义了代理的职责边界共四条约束这也是所有 Continue Check 共享的执行范式审查对象限定代理负责评估一个 PR 是否遵循文档中列出的 React 最佳实践目标是找出“不良实践”poor practice发现问题即修复一旦找到不良实践要直接以代码变更的方式修复且所有变更必须收敛在单个 commit内PR 描述必须可溯源创建 PR 时描述中必须引用所违反的具体最佳实践条目说明该变更对应哪条规则无问题则不动作如果没有发现任何不良实践就不开 PR。文档中还有一条关键的防发散约束IMPORTANT段只检查 PR 的 diff只建议与 PR 改动相邻或在其范围内的修改禁止在代码库中随机搜索“顺便改进”的机会。这一约束让 Check 的产出可预测、diff 可审查是这类自动审查规则能被团队信任的前提。二、Check 的执行机制从 Markdown 到 PR 审查从源码结构看这份 Markdown 文件并不是孤立的它由 CLI 的审查解析与 Check 状态管理两套代码消费规则来源解析。resolveReviews.ts 中的注释说明了审查规则的三个来源优先级其中第 3 级就是本地文件回退“Local.continue/agents/*.mdand.continue/checks/*.md(fallback)”且同名文件下 agents 目录优先于 checks 目录。也就是说本文的react-best-practices.md在未被云端配置覆盖时会作为本地回退规则被加载进 PR 审查流程。状态与接受/拒绝。checks.ts 实现了cn checks命令族支持三种用法cn checks [pr-url] # 列出 Check 状态并打印 diff cn checks accept [pr-url] # 接受所有待处理的建议 cn checks reject [pr-url] # 拒绝所有待处理的建议其内部行为可以从源码确认CheckStatus接口checks.ts定义了每个 Check 的statepending/success/failure、sessionId、commitMessage、suggestionStatus等字段——正好对应“单个 commit”约束每个 Check 的修复提交通过sessionId关联到具体 diff未提供 PR URL 时detectPrUrl()会从当前 git 分支和 remote URL 自动探测 PR支持 HTTPS 与 SSH 两种 remote 格式可选使用GITHUB_TOKEN环境变量提升 API 配额listChecks()会调用api/checks/status?pullRequestUrl...拉取状态逐条打印名称、描述、commit 信息与 diff并输出汇总行N passed / N failing / N pending退出码有明确语义0全部通过、1存在失败、2仍有 pending可直接接入 CI 判定acceptChecks()/rejectChecks()只处理suggestionStatus pending且存在commitMessage的 Check逐个调用agents/{sessionId}/accept|reject接口。因此这份 React 最佳实践 Check 的完整生命周期是本地 Markdown 定义规则 → 审查代理基于 PR diff 评估 → 发现问题则提交单个修复 commit 并生成 PR → 开发者用cn checks查看 diff、accept或reject建议。三、规则主体八大类 React 最佳实践以下规则按“影响从关键到增量”排序每条标注了影响等级CRITICAL / HIGH / MEDIUM / LOW IMPACT。文档开头特别注明如果项目启用了 React Compiler手动记忆化memo()、useMemo()、useCallback()与静态 JSX 提升会自动完成对应条目需相应弱化。1. 消除瀑布流Waterfalls文档将其定性为“头号性能杀手”每个串行await都会叠加一次完整的网络延迟。1.1 延迟 await 到真正需要时CRITICAL。把await移入实际使用数据的分支// ❌ Bad: blocks both branches async function handleRequest(userId: string, skip: boolean) { const userData await fetchUserData(userId); if (skip) return { skipped: true }; return processUserData(userData); } // ✅ Good: fetch only when needed async function handleRequest(userId: string, skip: boolean) { if (skip) return { skipped: true }; const userData await fetchUserData(userId); return processUserData(userData); }1.2 基于依赖的并行化CRITICAL。当操作之间存在部分依赖时用better-all最大化并行度// ❌ Bad: profile waits for config unnecessarily const [user, config] await Promise.all([fetchUser(), fetchConfig()]); const profile await fetchProfile(user.id); // ✅ Good: config and profile run in parallel import { all } from better-all; const { user, config, profile } await all({ async user() { return fetchUser(); }, async config() { return fetchConfig(); }, async profile() { return fetchProfile((await this.$.user).id); }, });注意profile通过this.$.user只依赖user无需等待config从而把原本“两批串行”压成“一批并行”。1.3 并行化独立操作CRITICAL。用Promise.all()或提前创建 Promise 立即启动独立操作// ❌ Bad: sequential execution, 3 round trips const user await fetchUser(); const posts await fetchPosts(); const comments await fetchComments(); // ✅ Good: parallel execution, 1 round trip const [user, posts, comments] await Promise.all([ fetchUser(), fetchPosts(), fetchComments(), ]); // ✅ Good: in API routes, start promises early export async function GET(request: Request) { const sessionPromise auth(); const configPromise fetchConfig(); const session await sessionPromise; const [config, data] await Promise.all([ configPromise, fetchData(session.user.id), ]); return Response.json({ data, config }); }第二种写法的要点是在 API 路由中尽早创建 Promisesession与config的取数在互相等待之前就已开始。1.4 策略性 Suspense 边界CRITICAL。用 Suspense 让包裹 UI 立即渲染数据流式到达// ❌ Bad: entire page blocked by data fetching async function Page() { const data await fetchData(); return ( div Sidebar / Header / DataDisplay data{data} / Footer / /div ); } // ✅ Good: layout renders immediately, data streams in function Page() { return ( div Sidebar / Header / Suspense fallback{Skeleton /} DataDisplay / /Suspense Footer / /div ); } async function DataDisplay() { const data await fetchData(); return div{data.content}/div; }2. 打包体积优化2.1 避免 Barrel 文件导入CRITICAL。文档指出 barrel 文件的导入本身可能耗费 200–800ms会加载数千个未使用模块// ❌ Bad: imports entire library import { Check, X, Menu } from lucide-react; // ✅ Good: imports only what you need import Check from lucide-react/dist/esm/icons/check; import X from lucide-react/dist/esm/icons/x; // ✅ Alternative: Next.js 13.5 optimizePackageImports // next.config.js module.exports { experimental: { optimizePackageImports: [lucide-react, mui/material] }, };2.2 条件模块加载HIGH。大模块只在功能激活时加载function AnimationPlayer({ enabled }: { enabled: boolean }) { const [frames, setFrames] useStateFrame[] | null(null); useEffect(() { if (enabled !frames typeof window ! undefined) { import(./animation-frames.js).then((mod) setFrames(mod.frames)); } }, [enabled, frames]); if (!frames) return Skeleton /; return Canvas frames{frames} /; }2.3 延迟非关键库MEDIUM。分析与日志类库在 hydration 之后加载// ❌ Bad: blocks initial bundle import { Analytics } from vercel/analytics/react; // ✅ Good: loads after hydration import dynamic from next/dynamic; const Analytics dynamic( () import(vercel/analytics/react).then((m) m.Analytics), { ssr: false }, );2.4 重型组件动态导入CRITICAL// ❌ Bad: Monaco bundles with main chunk (~300KB) import { MonacoEditor } from ./monaco-editor; // ✅ Good: Monaco loads on demand import dynamic from next/dynamic; const MonacoEditor dynamic( () import(./monaco-editor).then((m) m.MonacoEditor), { ssr: false }, );2.5 在用户意图出现时预加载MEDIUM。在 hover/focus 时预载重型 bundle降低感知延迟function EditorButton({ onClick }: { onClick: () void }) { const preload () { void import(./monaco-editor); }; return ( button onMouseEnter{preload} onFocus{preload} onClick{onClick} Open Editor /button ); }3. 服务端性能3.1 跨请求 LRU 缓存HIGH。React.cache()只在单请求内有效跨请求场景用 LRUimport { LRUCache } from lru-cache; const cache new LRUCachestring, any({ max: 1000, ttl: 5 * 60 * 1000 }); export async function getUser(id: string) { const cached cache.get(id); if (cached) return cached; const user await db.user.findUnique({ where: { id } }); cache.set(id, user); return user; }3.2 最小化 RSC 边界序列化HIGH。跨 Server/Client 边界只传客户端真正用到的字段// ❌ Bad: serializes all 50 fields async function Page() { const user await fetchUser(); return Profile user{user} /; } // ✅ Good: serializes only needed fields async function Page() { const user await fetchUser(); return Profile name{user.name} /; }3.3 组件组合实现并行取数CRITICAL。通过重构组件结构并行化取数// ❌ Bad: Sidebar waits for Pages fetch export default async function Page() { const header await fetchHeader(); return ( div div{header}/div Sidebar / /div ); } // ✅ Good: both fetch simultaneously async function Header() { const data await fetchHeader(); return div{data}/div; } export default function Page() { return ( div Header / Sidebar / /div ); }3.4 用 React.cache() 做单请求内去重MEDIUM。适用于鉴权与数据库查询文档同时注明 Next.js 会对fetch调用自动去重import { cache } from react; export const getCurrentUser cache(async () { const session await auth(); if (!session?.user?.id) return null; return await db.user.findUnique({ where: { id: session.user.id } }); });3.5 用 after() 调度非阻塞操作MEDIUM。日志/分析放在响应发出之后import { after } from next/server; export async function POST(request: Request) { await updateDatabase(request); after(async () { logUserAction({ userAgent: request.headers.get(user-agent) }); }); return Response.json({ status: success }); }4. 客户端数据获取4.1 使用被动事件监听器MEDIUM。给 touch/wheel 监听器加{ passive: true }让浏览器可以立即滚动// ✅ Good: allows browser to scroll immediately document.addEventListener(touchstart, handler, { passive: true }); document.addEventListener(wheel, handler, { passive: true });4.2 用 SWR 自动去重MEDIUM// ❌ Bad: each instance fetches separately const [users, setUsers] useState([]); useEffect(() { fetch(/api/users) .then((r) r.json()) .then(setUsers); }, []); // ✅ Good: multiple instances share one request import useSWR from swr; const { data: users } useSWR(/api/users, fetcher);4.3 为 localStorage 数据加版本号MEDIUM。加版本前缀、只存最小字段且始终 try-catch 包裹隐私模式下localStorage访问会抛异常const VERSION v2; function saveConfig(config: { theme: string }) { try { localStorage.setItem(config:${VERSION}, JSON.stringify(config)); } catch {} }5. 重渲染优化5.1 把状态读取推迟到使用点MEDIUM。只在回调中读取的状态不应订阅其变化// ❌ Bad: subscribes to all searchParams changes const searchParams useSearchParams(); const handleShare () { shareChat(searchParams.get(ref)); }; // ✅ Good: reads on demand const handleShare () { const ref new URLSearchParams(window.location.search).get(ref); shareChat(ref); };5.2 提取为记忆化组件MEDIUM。让昂贵计算可以因提前返回而跳过// ✅ Good: skips computation when loading const UserAvatar memo(function UserAvatar({ user }: { user: User }) { const id useMemo(() computeAvatarId(user), [user]); return Avatar id{id} /; }); function Profile({ user, loading }: Props) { if (loading) return Skeleton /; return UserAvatar user{user} /; }5.3 收窄 Effect 依赖LOW。用原始值代替对象从连续值派生布尔// ❌ Bad: re-runs on any user change useEffect(() { console.log(user.id); }, [user]); // ✅ Good: re-runs only when id changes useEffect(() { console.log(user.id); }, [user.id]); // ✅ Good: derive boolean from continuous value const isMobile width 768; useEffect(() { if (isMobile) enableMobileMode(); }, [isMobile]);5.4 订阅派生状态MEDIUM// ❌ Bad: re-renders on every pixel const width useWindowWidth(); const isMobile width 768; // ✅ Good: re-renders only on boolean change const isMobile useMediaQuery((max-width: 767px));5.5 函数式 setState 与惰性初始化MEDIUM。函数式更新避免闭包过期并产生稳定回调惰性初始化避免每次渲染都执行计算// ❌ Bad: stale closure risk, recreates callback const addItem useCallback( (item: Item) { setItems([...items, item]); }, [items], ); // ✅ Good: stable callback, always uses latest state const addItem useCallback((item: Item) { setItems((curr) [...curr, item]); }, []); // ❌ Bad: buildIndex runs every render const [index] useState(buildSearchIndex(items)); // ✅ Good: buildIndex runs only once const [index] useState(() buildSearchIndex(items));5.6 用 Transition 处理非紧急更新MEDIUMimport { startTransition } from react; useEffect(() { const handler () { startTransition(() setScrollY(window.scrollY)); }; window.addEventListener(scroll, handler, { passive: true }); return () window.removeEventListener(scroll, handler); }, []);6. 渲染性能6.1 长列表使用 CSS content-visibilityMEDIUM。文档称长列表场景可获得约 10 倍的初始渲染加速.message-item { content-visibility: auto; contain-intrinsic-size: 0 80px; }6.2 提升静态 JSX 元素HIGH。把静态 JSX尤其大型 SVG提取到组件外部复用// ✅ Good: reuses same element const skeleton div classNameh-20 animate-pulse bg-gray-200 /; function Container({ loading }: { loading: boolean }) { return div{loading skeleton}/div; }6.3 无闪烁地避免水合失配LOW。注入同步脚本在 React hydrate 前设置仅客户端可用的值function ThemeWrapper({ children }: { children: ReactNode }) { return ( div idtheme-wrapper{children}/div script dangerouslySetInnerHTML{{ __html: (function() { var theme localStorage.getItem(theme) || light; document.getElementById(theme-wrapper).className theme; })(); , }} / / ); }6.4 显式条件渲染MEDIUM。用三元表达式避免渲染出0或NaN// ❌ Bad: renders 0 when count is 0 { count Badge{count}/Badge; } // ✅ Good: renders nothing when count is 0 { count 0 ? Badge{count}/Badge : null; }7. JavaScript 性能7.1 批量处理 DOM CSS 变更MEDIUM。通过 class 或cssText分组修改减少 reflow// ❌ Bad: multiple reflows element.style.width 100px; element.style.height 200px; // ✅ Good: single reflow element.classList.add(highlighted-box);7.2 用 Set/Map 实现 O(1) 查找LOW// ❌ Bad: O(n) per check items.filter((item) allowedIds.includes(item.id)); // ✅ Good: O(1) per check const allowedSet new Set(allowedIds); items.filter((item) allowedSet.has(item.id));7.3 为重复查找构建索引 MapLOW// ❌ Bad: O(n) per lookup orders.map((o) ({ ...o, user: users.find((u) u.id o.userId) })); // ✅ Good: O(1) per lookup const userById new Map(users.map((u) [u.id, u])); orders.map((o) ({ ...o, user: userById.get(o.userId) }));7.4 缓存昂贵操作MEDIUM。缓存函数结果与存储 API 调用const cache new Mapstring, string(); function cachedSlugify(text: string): string { if (!cache.has(text)) cache.set(text, slugify(text)); return cache.get(text)!; } // Also cache localStorage reads function getLocalStorage(key: string) { if (!cache.has(key)) cache.set(key, localStorage.getItem(key)); return cache.get(key); }7.5 提前返回与长度检查MEDIUM。结果已确定就提前返回昂贵比较前先查长度// ✅ Good: early return function validateUsers(users: User[]) { for (const user of users) { if (!user.email) return { valid: false, error: Email required }; } return { valid: true }; } // ✅ Good: length check before sort function hasChanges(current: string[], original: string[]) { if (current.length ! original.length) return true; // ... expensive comparison }7.6 求最值用循环而非排序LOW// ❌ Bad: O(n log n) const sorted [...projects].sort((a, b) b.updatedAt - a.updatedAt); return sorted[0]; // ✅ Good: O(n) let latest projects[0]; for (const p of projects) { if (p.updatedAt latest.updatedAt) latest p; } return latest;7.7 用 toSorted() 保持不可变性MEDIUM。.sort()会原地修改数组破坏 React 的不可变模型// ❌ Bad: mutates original const sorted users.sort((a, b) a.name.localeCompare(b.name)); // ✅ Good: creates new array const sorted users.toSorted((a, b) a.name.localeCompare(b.name));8. 高级模式8.1 用 useEffectEvent 获得稳定回调引用LOW。当 effect 不应因回调变化而重新订阅时把回调存入 refimport { useEffectEvent } from react; function useWindowEvent(event: string, handler: () void) { const onEvent useEffectEvent(handler); useEffect(() { window.addEventListener(event, onEvent); return () window.removeEventListener(event, onEvent); }, [event]); }四、如何在本仓库之外应用这份规则把该 Check 用起来的路径很直接将 react-best-practices.md 复制到目标项目的.continue/checks/目录下frontmatter 中的name与description会被用于状态展示随后通过 PR 审查流程本地.continue/checks/*.md作为回退来源之一见 resolveReviews.ts 的解析逻辑触发评估再用cn checks [pr-url]查看各 Check 的 pending/success/failure 状态与 diffcn checks accept|reject [pr-url]批量处置建议退出码 0/1/2 可无缝嵌入 CI 判定checks.ts。三点使用建议按影响等级裁剪规则自带 CRITICAL/HIGH/MEDIUM/LOW 分级小团队可以先只启用 CRITICAL 级消除瀑布、barrel 导入、重组件动态导入逐步扩大审查面减少误报噪声结合 React Compiler 状态调整若目标项目已启用 React Compiler5.2、5.5 中的手动记忆化条目和 6.2 的静态 JSX 提升会被自动覆盖可在复制后删除或降级这些条目避免代理产出无效修改与同目录其他 Check 组合该文件与 anti-slop.md清理 AI 生成代码的冗余模式、security-audit.md 等共享同一套“单个 commit PR 描述引用规则 无问题不开 PR”的执行约束多 Check 并跑时互不干扰更广义的 PR 总体评审则可通过 general-review Action 的 GitHub Actions 工作流触发。这份文件的价值在于它把“React 性能优化经验”从口头规范变成了可执行、可审计、可批量接受/拒绝的 PR 流程资产——规则即提示词提示词即代理行为行为收敛为单 commit 可审查的 diff。【免费下载链接】continueopen-source coding agent项目地址: https://gitcode.com/GitHub_Trending/co/continue创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表