摘要:io/front-end-c…),2023 年初在公司做 Code Review 的时候顺手整理出来的。2026-05-08 晚上,我本来只想顺手改一下清单里过时的条目,结果一头扎进 Claude Code,两小时做了 22 次提交,把这个静态页面彻底改造了一遍。按语言选择清单:中文提问读中文版清单,英文提问读英文版清单。
写这篇博客的由头有点特别。
我有一个叫 front-end-checklist 的老项目(网页:wsafight.github.io/front-end-c…),2023 年初在公司做 Code Review 的时候顺手整理出来的。那会儿评审新同学的代码,总是在重复同样的话:"这里没做 XSS 转义"、"useEffect 里有竞态"、"label 没和 input 关联"。后来干脆把这些反复出现的问题写成清单,用 Jekyll 挂在 GitHub Pages 上。
front-end-checklist
然后它就一直在那儿积灰。2024 只有一次提交,2025 只有一次,到了 2026 年 5 月也还没动过。
2026-05-08 晚上,我本来只想顺手改一下清单里过时的条目,结果一头扎进 Claude Code,两小时做了 22 次提交,把这个静态页面彻底改造了一遍。
真正让我想写这篇文章的,不是"AI 让我写代码变快"。快是肯定快,但这次改造最后落到了一件我之前没想过的事情上——一份静态文档,换了个交付形式,才真正开始被用起来。
先交代清楚这个清单是什么。
它不是 ESLint 能查的那种风格规则,而是 Code Review 时需要结合业务和上下文判断的问题。目前一共 25 个分组、160 条检查项:
命名规范 数据与类型 函数设计 状态管理 控制流 异步处理 数据请求 UI 与渲染 样式与响应式 路由与权限 性能 安全与健壮性 表单与交互 错误处理 测试 无障碍访问 用户体验 代码质量 工程化 国际化 日志与监控 依赖管理 浏览器兼容 文档与协作 PR 自检
随便挑几条看看:
区分"缺失"、"为空"、"为 0"、"为空数组"、"为空字符串"的业务含义 处理并发请求的竞态问题,避免旧响应覆盖新状态 同一表单/输入控件不要在受控与非受控之间切换 useEffect / watch 的依赖项必须完整,避免闭包捕获过期值 定时器和事件监听记得清除,否则可能引发内存泄漏 表单错误应定位到具体字段,而不是只给出笼统提示
useEffect
watch
这些不是靠格式化、类型推导或者简单 AST 规则就能稳定抓出来的问题。它们往往来自真实线上事故、返工、误解和交接成本。以前这些经验只能靠人去记,记不住就会在别的项目里重新踩一次。
清单做完这三年,我一直有个遗憾:没人真的会去翻。
我自己都不翻。做一个 PR 的时候,我知道清单里有条"处理并发请求的竞态",但我会不会每次都老老实实打开页面对一遍?不会。同事更不会。新人入职时我会把链接丢给他们,他们收藏,然后就再也不打开了。
这是很多静态文档共同的问题:信息在那里,但你必须主动去触达它。而在 CR 场景里,主动触达的前提是你已经意识到自己可能漏了什么。可真正漏掉的时候,人往往意识不到。
我试过一些办法:
根本问题不是文档形式不够花哨,而是查阅式文档很难主动进入人的工作流。
Claude Code 的 Skill 机制改变了这件事。
简单说,Skill 是一段给 AI 读的说明书,加上它执行任务时需要参考的资料。当你在对话里说到特定触发词时,AI 会加载这个 Skill,并按说明书走流程。
我的 frontend-checklist Skill 里写的是这样的逻辑:
frontend-checklist
/frontend-checklist
git diff
关键的翻转在这里:
以前是"你去翻清单"。现在是"清单来找你"。
写 PR 的时候,你不需要记得清单里每一条是什么,直接在 IDE 里说一句"按前端清单 review 我这个 PR",AI 会把 160 条和当前 diff 放在一起看,只告诉你命中的那些,并给出文件路径和行号,必要时附 1-3 行示例代码。心智负担从"记住 160 条"降到"知道有这么一个入口"。
空讲没意思,看一段 showcase 里的实际输出。
下面这段代码是我写的一个有意设计的"坏例子"(showcase/cases/01-xss/bad.tsx),一个评论列表组件:
showcase/cases/01-xss/bad.tsx
export function CommentList(props: any) { const [list, setList] = useState([] as any); const [keyword, setKeyword] = useState(''); useEffect(() => { fetch('/api/comments?topic=' + props.topic) .then((r) => r.json()) .then((d) => { setList(d.data); }); }, [props.topic]); const highlight = (text, kw) => { if (!kw) return text; return text.replace(new RegExp(kw, 'g'), '<mark>' + kw + '</mark>'); }; return ( <div> <input type="text" onChange={onSearch} /> <div id="tip" dangerouslySetInnerHTML={{ __html: props.tip }} /> {list.map((c: Comment, i: number) => ( <div key={i} className="comment"> <img src={c.avatar} /> <a href={'javascript:void(0)'} onClick={() => eval(c.author.onClick)}> {c.author.name} </a> <div dangerouslySetInnerHTML={{ __html: highlight(c.body, keyword) }} /> </div> ))} </div> ); }
乍看能跑,TypeScript 不一定报错,ESLint 也不一定能拦住关键问题。但 Skill 跑完吐出来 11 条命中,挑几条看(行号对应 showcase/cases/01-xss/bad.tsx 原文件,不是上面代码块里的相对行号):
安全与健壮性(最严重的一批)
line 35
dangerouslySetInnerHTML={{ __html: props.tip }}
tip
line 44
highlight
dangerouslySetInnerHTML
body
keyword
String.split
<mark>
line 39
eval(c.author.onClick)
数据请求
line 14-20
props.topic
AbortController
ignore
UI 与渲染
line 37
key={i}
c.id
无障碍
line 34, 38
aria-label
label
<img>
alt
泛泛地让 AI 做 review,它很容易给出"结构清晰、建议补充错误处理"这类通用意见。扔给 ESLint,也大概率只会在 any、未定义变量、hook 依赖这类规则上发声。Skill 的价值在于把评审标准显式化:AI 不是凭感觉聊几句,而是按一把真实的尺子在量代码。
any
我还拿其他 showcase 跑了一遍:用户资料页命中 12 条(竞态、未清理副作用、无空值守卫),注册表单命中 17 条(a11y、字段级错误、密码明文 input、防重复提交)。这些都是一眼看过去"差不多能用",但上线后很容易变成坑的代码。
Skill 装起来一条命令的事:
curl -fsSL https://github.com/wsafight/front-end-checklist/releases/latest/download/install.sh \ | sh -s -- claude
装完在对话里说 /frontend-checklist 或"按前端清单 review 我这个 PR",就会触发它。也支持 Kiro / Cursor / Codex,把命令结尾换一下就行。
清单是中英双语的,AI 会根据你提问的语言自动选对应版本。
两小时改造一个老项目听起来像标题党,但实际发生的事情比这更有意思:一份躺了三年没人翻的清单,换了个交付形式,突然就活过来了。内容还是那 160 条,变的只是"它怎么到达读者"。
如果你手里也有这种"明明有价值但没人用"的老文档,值得花个晚上,把它接进 AI 看看。
— 2026-05-09 夜
暂无回复,快来抢沙发吧!
本次需消耗银元:
100
当前账户余额: 0 银元
引子:一个吃灰三年的项目被重新盘活
写这篇博客的由头有点特别。
我有一个叫
front-end-checklist的老项目(网页:wsafight.github.io/front-end-c…),2023 年初在公司做 Code Review 的时候顺手整理出来的。那会儿评审新同学的代码,总是在重复同样的话:"这里没做 XSS 转义"、"useEffect 里有竞态"、"label 没和 input 关联"。后来干脆把这些反复出现的问题写成清单,用 Jekyll 挂在 GitHub Pages 上。然后它就一直在那儿积灰。2024 只有一次提交,2025 只有一次,到了 2026 年 5 月也还没动过。
2026-05-08 晚上,我本来只想顺手改一下清单里过时的条目,结果一头扎进 Claude Code,两小时做了 22 次提交,把这个静态页面彻底改造了一遍。
真正让我想写这篇文章的,不是"AI 让我写代码变快"。快是肯定快,但这次改造最后落到了一件我之前没想过的事情上——一份静态文档,换了个交付形式,才真正开始被用起来。
清单本身:三年沉淀下来的 160 条检查项
先交代清楚这个清单是什么。
它不是 ESLint 能查的那种风格规则,而是 Code Review 时需要结合业务和上下文判断的问题。目前一共 25 个分组、160 条检查项:
随便挑几条看看:
这些不是靠格式化、类型推导或者简单 AST 规则就能稳定抓出来的问题。它们往往来自真实线上事故、返工、误解和交接成本。以前这些经验只能靠人去记,记不住就会在别的项目里重新踩一次。
痛点:清单挂在网上,但不会进入工作流
清单做完这三年,我一直有个遗憾:没人真的会去翻。
我自己都不翻。做一个 PR 的时候,我知道清单里有条"处理并发请求的竞态",但我会不会每次都老老实实打开页面对一遍?不会。同事更不会。新人入职时我会把链接丢给他们,他们收藏,然后就再也不打开了。
这是很多静态文档共同的问题:信息在那里,但你必须主动去触达它。而在 CR 场景里,主动触达的前提是你已经意识到自己可能漏了什么。可真正漏掉的时候,人往往意识不到。
我试过一些办法:
根本问题不是文档形式不够花哨,而是查阅式文档很难主动进入人的工作流。
转折:把清单变成 AI 的上下文
Claude Code 的 Skill 机制改变了这件事。
简单说,Skill 是一段给 AI 读的说明书,加上它执行任务时需要参考的资料。当你在对话里说到特定触发词时,AI 会加载这个 Skill,并按说明书走流程。
我的
frontend-checklistSkill 里写的是这样的逻辑:/frontend-checklist、"按前端清单 review"、"按 checklist 检查这段代码"。用户没提,它不会自作主张去扫代码。git diff定位变更;如果都没有,就先确认范围,不盲目扫整个仓库。关键的翻转在这里:
写 PR 的时候,你不需要记得清单里每一条是什么,直接在 IDE 里说一句"按前端清单 review 我这个 PR",AI 会把 160 条和当前 diff 放在一起看,只告诉你命中的那些,并给出文件路径和行号,必要时附 1-3 行示例代码。心智负担从"记住 160 条"降到"知道有这么一个入口"。
一个真实跑出来的 review
空讲没意思,看一段 showcase 里的实际输出。
下面这段代码是我写的一个有意设计的"坏例子"(
showcase/cases/01-xss/bad.tsx),一个评论列表组件:export function CommentList(props: any) { const [list, setList] = useState([] as any); const [keyword, setKeyword] = useState(''); useEffect(() => { fetch('/api/comments?topic=' + props.topic) .then((r) => r.json()) .then((d) => { setList(d.data); }); }, [props.topic]); const highlight = (text, kw) => { if (!kw) return text; return text.replace(new RegExp(kw, 'g'), '<mark>' + kw + '</mark>'); }; return ( <div> <input type="text" onChange={onSearch} /> <div id="tip" dangerouslySetInnerHTML={{ __html: props.tip }} /> {list.map((c: Comment, i: number) => ( <div key={i} className="comment"> <img src={c.avatar} /> <a href={'javascript:void(0)'} onClick={() => eval(c.author.onClick)}> {c.author.name} </a> <div dangerouslySetInnerHTML={{ __html: highlight(c.body, keyword) }} /> </div> ))} </div> ); }乍看能跑,TypeScript 不一定报错,ESLint 也不一定能拦住关键问题。但 Skill 跑完吐出来 11 条命中,挑几条看(行号对应
showcase/cases/01-xss/bad.tsx原文件,不是上面代码块里的相对行号):安全与健壮性(最严重的一批)
line 35:dangerouslySetInnerHTML={{ __html: props.tip }}直接吃外部tip,存在 XSS 风险。建议默认文本渲染,真要富文本时先用 DOMPurify 消毒。line 44:highlight用字符串拼接 HTML 再dangerouslySetInnerHTML,body和keyword都没转义。建议改成String.split分段渲染,把命中段包进<mark>,不要注入 HTML。line 39:eval(c.author.onClick)把接口返回的字符串当代码执行,等于把任意脚本执行权交给接口数据。建议彻底删除,交互改成前端静态映射。数据请求
line 14-20:props.topic变化时发起新请求但没取消旧请求,旧响应可能覆盖新数据。建议用AbortController或ignore标志在 cleanup 里关掉。UI 与渲染
line 37:key={i}用数组下标,列表增删重排时可能导致状态错位。建议用稳定的业务 id,比如c.id。无障碍
line 34, 38:搜索 input 没有aria-label或关联label,<img>没有alt。泛泛地让 AI 做 review,它很容易给出"结构清晰、建议补充错误处理"这类通用意见。扔给 ESLint,也大概率只会在
any、未定义变量、hook 依赖这类规则上发声。Skill 的价值在于把评审标准显式化:AI 不是凭感觉聊几句,而是按一把真实的尺子在量代码。我还拿其他 showcase 跑了一遍:用户资料页命中 12 条(竞态、未清理副作用、无空值守卫),注册表单命中 17 条(a11y、字段级错误、密码明文 input、防重复提交)。这些都是一眼看过去"差不多能用",但上线后很容易变成坑的代码。
怎么用
Skill 装起来一条命令的事:
装完在对话里说
/frontend-checklist或"按前端清单 review 我这个 PR",就会触发它。也支持 Kiro / Cursor / Codex,把命令结尾换一下就行。清单是中英双语的,AI 会根据你提问的语言自动选对应版本。
两小时改造一个老项目听起来像标题党,但实际发生的事情比这更有意思:一份躺了三年没人翻的清单,换了个交付形式,突然就活过来了。内容还是那 160 条,变的只是"它怎么到达读者"。
如果你手里也有这种"明明有价值但没人用"的老文档,值得花个晚上,把它接进 AI 看看。
— 2026-05-09 夜