1. 为什么要把 react-best-practices 封装成 Agent Skill
React 项目的性能问题有个很讨厌的特点:它不会在编译时报错,也不会在测试里挂掉,只会在用户点开页面时悄悄多等 600ms。等团队发现时,往往已经积累了几十个页面的性能债。Vercel 开源的 react-best-practices 把十余年 React/Next.js 优化经验整理成了结构化规则集,但规则集本身只是文档,真正让它产生价值的方式,是把它变成一个 Agent Skill,让 AI 在写代码和做 Code Review 时自动调用。
Agent Skill 和普通 Prompt 的区别,我用一个类比说明:Prompt 像是你临时跟同事口头交代"帮我看看这段代码有没有性能问题",每次说法不同、标准不同;Skill 则像是一份写进团队 Wiki 的检查清单,有明确的触发条件、输入输出约定和约束规则。在工程化 Agent 系统里,Skill 更接近"函数"或"子代理"——它定义了 Agent 能做什么、在什么条件下做、做到什么程度。
react-best-practices 这套规则的设计思路很值得借鉴:它按影响优先级排序,从 CRITICAL 到 LOW 分级,强制先解决对用户体验影响最大的问题。比如消除异步瀑布流和客户端 Bundle 体积优化被标为 CRITICAL,而重渲染优化、JS 性能属于 MEDIUM-LOW。这个优先级逻辑如果只放在文档里,开发者大概率还是会先花时间调 useMemo;但封装成 Skill 后,AI 会按规则等级输出问题清单,把请求瀑布流排在重渲染前面。
我试过把类似规则集直接塞进系统提示词,效果并不好——规则一多,模型就开始"选择性遗忘",或者把不同等级的规则混在一起输出。后来改成 Skill 结构,把触发条件、规则文件、输出模板分开管理,稳定性明显提升。这篇文章就按这个思路,带你从目录结构开始,一步步把 react-best-practices 沉淀成一个可复用的 Agent Skill,并用一段真实的 React 组件代码验证它的检查与修复建议能力。
2. TaoToken 前置准备:给 Skill 一个稳定的模型入口
Skill 本身是规则和提示词的封装,但它最终要调用模型来执行检查。如果你用的是 Claude Code、Cursor 或 Codex 这类编码智能体,模型入口的稳定性直接决定了 Skill 能不能在团队里长期跑下去。TaoToken 在这里扮演的角色是统一的 API 接入层,让你不用在每个工具里分别配置不同的模型供应商。
先明确三个核心要素,后面配置里会反复用到:
| 要素 | 值 | 说明 |
|---|---|---|
| Base URL | https://taotoken.net/api | 所有请求的统一入口,不加 UTM 参数 |
| API Key | 在控制台创建 | 形如sk-开头的字符串,注意保密 |
| Model ID | 按需选择 | 代码审查建议用长上下文模型,如claude-sonnet-4-20250514 |
获取 Key 的路径是:登录官网后进入控制台,在 API Keys 页面创建新密钥。这里有个容易踩的坑:创建后 Key 只显示一次,务必立刻复制到安全的地方,页面刷新后就看不到了。
如果你用的是 Claude Code,它读取的是环境变量或 settings 文件;如果用 Cline 或 Cursor,配置方式又不一样。为了后面演示方便,我建议先把 Key 存到环境变量里:
export TAOTOKEN_API_KEY="sk-你的密钥" export TAOTOKEN_BASE_URL="https://taotoken.net/api"验证 Key 是否可用,最直接的方式是发一个最小请求:
curl -s https://taotoken.net/api/v1/messages \ -H "x-api-key: $TAOTOKEN_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "max_tokens": 64, "messages": [{"role": "user", "content": "回复 OK 两个字母"}] }'返回里能看到content字段带OK,说明 Key 和 Base URL 都通了。这一步别跳过,后面 Skill 报错时你才能快速判断是模型入口问题还是规则配置问题。
对于长期做代码审查和 Agent 任务的场景,Coding Plan 比按量计费更划算,尤其是团队多人共用时。你可以先按量跑通流程,确认 Skill 效果后再切到套餐。模型对话入口可以用来单独测试某条规则对一段代码的判断,方便调试提示词。
3. Skill 目录结构与可复制配置
把 react-best-practices 封装成 Skill,核心是把"规则"和"执行逻辑"分离。规则文件保持和上游仓库一致的结构,执行逻辑用一份配置文件描述触发条件和输出格式。下面是我实际用的目录结构:
skills/ └── react-best-practices/ ├── SKILL.md # 技能主文件,定义触发条件与输出约定 ├── rules/ # 规则文件,按类别前缀命名 │ ├── async-parallel.md │ ├── async-defer-await.md │ ├── bundle-dynamic-import.md │ └── rerender-memo.md ├── templates/ │ └── review-output.md # 输出模板,约束问题清单格式 └── skill.config.json # 技能元数据与模型参数SKILL.md是触发入口,它决定了 Agent 什么时候调用这个技能。内容要写得足够具体,避免"代码审查"这种宽泛描述导致误触发:
--- name: react-best-practices description: 审查 React/Next.js 代码的性能问题,按 CRITICAL 到 LOW 优先级输出问题清单与修复建议。当用户提交 .tsx/.jsx 文件、要求 Code Review、或提到性能优化时触发。 --- # React 最佳实践审查技能 ## 触发条件 - 用户提供 React 组件代码并要求审查 - 用户提到"性能""卡顿""首屏慢""请求瀑布流" - 代码变更涉及 useEffect、数据获取、动态导入 ## 执行步骤 1. 读取 rules/ 下所有规则文件 2. 按影响等级分组:CRITICAL > HIGH > MEDIUM > LOW 3. 对每段代码逐条匹配规则,记录违规位置 4. 按 templates/review-output.md 格式输出 ## 约束 - 不修改代码,只输出问题与建议 - 每条问题必须引用具体规则文件名 - 无问题的规则不输出,避免噪音skill.config.json里放模型参数和规则路径,这样换模型时不用改 SKILL.md:
{ "name": "react-best-practices", "version": "1.0.0", "model": "claude-sonnet-4-20250514", "baseUrl": "https://taotoken.net/api", "rulesDir": "./rules", "outputTemplate": "./templates/review-output.md", "maxTokens": 4096, "temperature": 0.2 }temperature设成 0.2 是有意的——代码审查需要稳定输出,太高的随机性会让同一段代码两次审查结果不一致,团队协作时很难对齐。
规则文件沿用上游的模板格式,每条规则必须包含影响等级、标签和错误/正确示例。以async-parallel.md为例:
--- impact: CRITICAL tags: [async, waterfall, performance] --- # 并行化无关请求 ## 问题 多个不相互依赖的异步请求被串行 await,总耗时等于各请求之和。 ## 错误示例 ```ts const user = await fetchUser(id); const posts = await fetchPosts(user.id); const settings = await fetchSettings(user.id);正确示例
const user = await fetchUser(id); const [posts, settings] = await Promise.all([ fetchPosts(user.id), fetchSettings(user.id) ]);修复建议
识别无数据依赖的请求,用 Promise.all 并行执行。
输出模板 `review-output.md` 约束了问题清单的格式,让不同人跑出来的结果结构一致: ```markdown ## 审查结果 ### CRITICAL - [规则名] 文件:行号 — 问题描述 - 修复建议:... ### HIGH ... ### 统计 - 检查规则数:N - 发现问题数:M这套结构的好处是:规则可以独立增删,不影响主流程;输出格式固定,方便接入 CI 或生成报告;模型参数集中管理,换供应商只改一个文件。如果你团队用 Cline 的 MCP 模式,可以把skill.config.json里的 Base URL、Key、Model ID 三件套直接映射到 MCP 配置里,保持和 Skill 一致。
4. 验证请求:对一段 React 组件执行检查
配置写完后必须验证,否则你不知道 Skill 是真的在按规则检查,还是模型在自由发挥。我准备了一段故意埋了三个问题的组件代码,覆盖请求瀑布流、Bundle 膨胀和重渲染三类问题:
// UserDashboard.tsx import HeavyChart from './HeavyChart'; import { useEffect, useState } from 'react'; export function UserDashboard({ userId }: { userId: string }) { const [user, setUser] = useState(null); const [posts, setPosts] = useState([]); const [settings, setSettings] = useState(null); useEffect(() => { async function load() { const u = await fetch(`/api/user/${userId}`).then(r => r.json()); setUser(u); const p = await fetch(`/api/posts/${u.id}`).then(r => r.json()); setPosts(p); const s = await fetch(`/api/settings/${u.id}`).then(r => r.json()); setSettings(s); } load(); }, [userId]); return ( <div> <h1>{user?.name}</h1> <HeavyChart data={posts} /> <span>{settings?.theme}</span> </div> ); }这段代码的问题很典型:三个请求串行执行形成瀑布流;HeavyChart静态导入导致首屏 Bundle 膨胀;settings变化会触发整个组件重渲染。现在把代码和 Skill 一起发给模型:
curl -s https://taotoken.net/api/v1/messages \ -H "x-api-key: $TAOTOKEN_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "max_tokens": 4096, "temperature": 0.2, "system": "你是 react-best-practices 技能执行器。读取 rules/ 下规则,按 CRITICAL 到 LOW 输出问题清单,每条引用规则文件名。", "messages": [{ "role": "user", "content": "审查以下代码:\n\n```tsx\nimport HeavyChart from '\''./HeavyChart'\'';\nimport { useEffect, useState } from '\''react'\'';\n\nexport function UserDashboard({ userId }: { userId: string }) {\n const [user, setUser] = useState(null);\n const [posts, setPosts] = useState([]);\n const [settings, setSettings] = useState(null);\n\n useEffect(() => {\n async function load() {\n const u = await fetch(`/api/user/${userId}`).then(r => r.json());\n setUser(u);\n const p = await fetch(`/api/posts/${u.id}`).then(r => r.json());\n setPosts(p);\n const s = await fetch(`/api/settings/${u.id}`).then(r => r.json());\n setSettings(s);\n }\n load();\n }, [userId]);\n\n return (\n <div>\n <h1>{user?.name}</h1>\n <HeavyChart data={posts} />\n <span>{settings?.theme}</span>\n </div>\n );\n}\n```" }] }'预期输出应该包含三条 CRITICAL/HIGH 级别的问题,每条都引用具体规则文件。实际返回的content里,问题清单大致长这样:
### CRITICAL - [async-parallel.md] UserDashboard.tsx:12-16 — 三个请求串行 await,形成请求瀑布流 - 修复建议:user 请求完成后,posts 和 settings 无依赖关系,用 Promise.all 并行 ### CRITICAL - [bundle-dynamic-import.md] UserDashboard.tsx:2 — HeavyChart 静态导入,进入首屏 Bundle - 修复建议:改用 React.lazy + Suspense 动态导入 ### MEDIUM - [rerender-memo.md] UserDashboard.tsx:26 — settings 变化触发整个组件重渲染 - 修复建议:将 settings 消费逻辑拆到子组件,或用 useMemo 隔离验证成功的标志有三个:问题按优先级排序、每条引用规则文件名、修复建议具体到代码行。如果输出只是泛泛而谈"建议优化性能",说明 Skill 的规则文件没被正确读取,需要检查rulesDir路径和 SKILL.md 里的执行步骤。
修复后的代码可以再跑一次验证,确认问题清零:
import { lazy, Suspense, useEffect, useState } from 'react'; const HeavyChart = lazy(() => import('./HeavyChart')); export function UserDashboard({ userId }: { userId: string }) { const [user, setUser] = useState(null); const [posts, setPosts] = useState([]); const [settings, setSettings] = useState(null); useEffect(() => { async function load() { const u = await fetch(`/api/user/${userId}`).then(r => r.json()); setUser(u); const [p, s] = await Promise.all([ fetch(`/api/posts/${u.id}`).then(r => r.json()), fetch(`/api/settings/${u.id}`).then(r => r.json()) ]); setPosts(p); setSettings(s); } load(); }, [userId]); return ( <div> <h1>{user?.name}</h1> <Suspense fallback={<div>加载图表...</div>}> <HeavyChart data={posts} /> </Suspense> <span>{settings?.theme}</span> </div> ); }第二次审查应该只输出"未发现 CRITICAL 问题",或者只剩 MEDIUM 级别的重渲染建议。这个前后对比就是 Skill 有效性的直接证据。
5. 常见报错排查:401、local proxy failed 与 reading choices
Skill 跑不起来时,报错信息往往指向配置问题而不是规则问题。下面是我和团队实际遇到过的几类,按出现频率排序。
401 Unauthorized是最常见的。返回体通常是{"error":{"type":"authentication_error","message":"invalid x-api-key"}}。原因有三个:Key 复制时带了空格、Key 已过期或被删除、请求头字段名写错。Anthropic 格式用x-api-key,OpenAI 格式用Authorization: Bearer,两者不能混。排查时先用第 2 节的 curl 最小请求验证 Key,确认通了再查 Skill 配置。
local proxy failed一般出现在 Cline 或 Cursor 这类工具里,报错形如Error: connect ECONNREFUSED 127.0.0.1:xxxx。这说明工具在尝试走本地代理端口,但代理没启动。检查工具的 settings 里是否残留了http.proxy配置,或者环境变量里有HTTP_PROXY。把 Base URL 直接设成https://taotoken.net/api,不要经过任何中间层。如果团队统一用 MCP 模式,确认 MCP server 的启动命令里没有多余的代理参数。
reading 'choices'这个报错很有迷惑性,完整信息通常是Cannot read properties of undefined (reading 'choices')。它意味着工具按 OpenAI 格式解析响应,但实际返回的是 Anthropic 格式,或者反过来。根源在skill.config.json里的模型和接口格式不匹配。用claude-sonnet-4-20250514就要走/v1/messages接口,返回体是content数组;用 GPT 系列走/v1/chat/completions,返回体才是choices。检查你的请求路径和模型 ID 是否对应。
OAuth 相关报错比如OAuth token expired或invalid_grant,通常出现在 Claude Code 的登录态失效时。如果你是用 API Key 接入,不应该出现 OAuth 报错;一旦出现,说明工具还在走账号登录模式。需要在 Claude Code 的 settings 里显式配置 API Key 和 Base URL,覆盖掉默认的 OAuth 流程。Codex 的auth.json也是同理,里面如果存的是 OAuth token 而不是 API Key,就会报这个错。正确做法是把auth.json改成:
{ "apiKey": "sk-你的密钥", "baseUrl": "https://taotoken.net/api" }规则文件读取失败表现为模型输出"未找到规则"或直接忽略规则自由发挥。检查rulesDir是相对路径还是绝对路径,相对路径的基准是 SKILL.md 所在目录。另外确认规则文件的 frontmatter 格式正确,impact字段值必须是 CRITICAL/HIGH/MEDIUM/LOW 之一,写错会导致该规则被跳过。
排查顺序建议固定下来:先验证 Key 和 Base URL(curl 最小请求),再验证接口格式(模型 ID 与路径匹配),最后验证规则加载(单独发一条规则文件内容让模型复述)。这样能把问题范围快速缩小到某一层,不用在配置里大海捞针。
6. 把 Skill 接入你的日常工作流
Skill 配好之后,真正的价值在于让它进入日常流程,而不是每次手动发 curl。三种接入方式按投入从低到高排列。
最轻的方式是把它做成一个 shell 脚本,接收文件路径作为参数,输出审查报告:
#!/bin/bash # review.sh FILE=$1 CODE=$(cat "$FILE") curl -s https://taotoken.net/api/v1/messages \ -H "x-api-key: $TAOTOKEN_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d "{ \"model\": \"claude-sonnet-4-20250514\", \"max_tokens\": 4096, \"temperature\": 0.2, \"system\": \"$(cat skills/react-best-practices/SKILL.md)\", \"messages\": [{\"role\": \"user\", \"content\": \"审查:\n$CODE\"}] }" | jq -r '.content[0].text'配合 git hook,在 pre-commit 阶段对改动的.tsx文件跑一遍,问题清单直接打印到终端。这样性能问题在提交前就被拦住,不用等到 Code Review。
中等投入是接入 CI。在 GitHub Actions 里加一个 job,对 PR 里改动的 React 文件执行审查,把结果作为评论贴到 PR 上。关键是把temperature压到 0.2 以下,保证同一份代码在不同 CI 运行里输出一致,否则评论会反复变化,团队会失去信任。
最重但收益最大的是做成团队共享的 Skill 仓库。把skills/react-best-practices/作为独立 git 仓库维护,规则文件按上游更新同步,团队成员的编码工具统一从这个仓库拉取。新人入职时不用背规则,AI 会在他们写代码时按统一标准提示。规则编号(如async-parallel)还能作为 Code Review 的引用依据,避免"我觉得这样更好"的主观争论。
一个实用技巧:定期用pnpm validate或类似脚本扫描项目代码,统计各类问题的出现次数,做成趋势图。如果 CRITICAL 问题数在下降,说明 Skill 在起作用;如果某类问题反复出现,说明对应规则的提示词需要加强,或者团队需要针对性培训。这比单纯看"AI 有没有报错"更能反映 Skill 的实际效果。
最后提醒一点:Skill 的输出是建议,不是命令。模型偶尔会误报,比如把有依赖关系的请求判成可并行。修复建议落地前,人工确认一遍数据依赖关系。把 Skill 当成一个不知疲倦的初级审查员,而不是最终决策者,这样用起来最稳。