从 onSelect?.() 到 onSelect():HumanLayer Skills 移除可选回调的完整实战指南
【免费下载链接】skills项目地址: https://gitcode.com/GitHub_Trending/skills53/skills
HumanLayer Skills(skills53/skills)是 HumanLayer 开源的 Claude Code 技能合集,其中narrow-react-prop-types 技能专攻一个高频痛点:把 React 组件中形如onSelect?.(...)的可选回调收窄为必须提供的onSelect(...),让 TypeScript 类型只描述真实运行代码的状态。本文将带你一步步了解这套"移除可选回调"的方法论。
为什么onSelect?.()是类型腐化的信号?
在 React 项目中,组件的 props 往往会随着 Storybook 故事、测试 mock、演示代码不断"变宽"。最典型的征兆就是:
- 组件接口里塞满了可选字段(
onSelect?、onArchive?、onRename?) - 内部用
onSelect?.(item)防御式调用,生怕回调没传 - 用
items ?? []、count ?? 0兜底那些运行时代码里其实总是有值的 prop
问题在于:每一个可选回调都意味着组件必须额外处理一条分支。菜单项明明永远渲染,点击却可能"点了没反应"——这就是可选回调制造出的"死区状态"。
HumanLayer Skills 的核心理念是:以真实业务调用方(live code paths)为唯一事实来源,让类型尽可能严格,把不可能表达的状态挡在类型系统之外。
安装 narrow-react-prop-types 技能:一键上手步骤
安装非常简单,在你的项目目录中执行:
npx skills add humanlayer/skills --skill narrow-react-prop-types然后在 Claude Code 中直接输入/narrow-react-prop-types即可启动技能。完整安装说明见 README.md。
如果想 clone 整个仓库研究源码,可使用:
git clone https://gitcode.com/GitHub_Trending/skills53/skills移除可选回调的 11 步工作流
该技能的完整方法论定义在 SKILL.md,共 11 个步骤,可以概括为"找 → 查 → 改 → 验"四阶段:
1️⃣ 识别"嫌疑组件"
技能列出了 5 类高危信号,其中最核心的就是可选回调调用(onSelect?.(...)、onArchive?.(...))和兜底状态处理(items ?? [])。
💡 注意:不要仅凭一个 story 或测试就锁定目标——故事和测试只能证明"类型被放宽了",不能证明某个状态是真实存在的。
2️⃣ 穷举所有调用方并分类
搜索组件及其导出类型的所有引用,然后分成两类:
| 类别 | 包含内容 | 作用 |
|---|---|---|
| 真实代码路径 | 应用路由、providers、hooks、生产包导出 | ✅ 决定 props 契约的唯一依据 |
| 支撑代码 | Storybook、测试、fixtures、mock | ⚠️ 只作辅助证据 |
3️⃣ 逐个 prop 判定:必填、可选还是删除
这是移除可选回调的关键判据:
- 必填:所有非测试、非 Storybook 调用方都传了这个 prop
- 可选:至少一个真实调用方没传,且"不传"本身是有意义的运行时状态
- 删除:没有任何真实调用方使用它(往往只是当年为 Storybook 加的)
一个容易混淆的细节:可空 ≠ 可选。如果真实代码总是传值但值可能为空,应写成必填的可空 prop(focusedItem: FocusedItem | null),而不是focusedItem?: FocusedItem | null。
4️⃣ 收紧类型,并"由外向内"传播
这是本技能最精华的部分:收紧不能只停在导出的组件上。
如果父组件的 props 收窄后,传给子组件的 handler 也总是有了,那么子组件内部的
onRename?.(id, name)就应直接改为必须调用:
onRename(id, name)技能还强调了一条铁律:如果组件总是渲染一个可交互元素(按钮、菜单项),就必须要求对应的 handler 为必填——绝不允许出现"看得见的按钮点了没反应"的死区状态。
5️⃣ 删除只为宽类型服务的兜底逻辑
可选回调移除后,一堆防御式代码会失去存在意义:
// 之前 new Set(expandedIds ?? defaultExpandedIds ?? []) // 之后 new Set(expandedIds)// 之前 items && items.length > 0 // 之后 items.length > 06️⃣ 让测试和故事去适应真实代码
收窄 props 后,story 或测试报错了怎么办?反向修复:给故事补上真实合理的 handler 和状态,而不是为了让测试省事把 prop 重新改成可选。如果测试配置变得啰嗦,应创建测试辅助函数(fixture)来满足严格契约,并把它留在支撑代码一侧。
7️⃣ 类型检查验证
最后对改动的包和所有消费方跑类型检查(本技能在 monorepo 中推荐bun --bun run typecheck --filter <package>),确保共享包与消费应用都通过编译。
CI 自动化:让 AI Agent 定时执行类型收窄
这套技能不只用于手动执行,HumanLayer 还把它做成了GitHub Actions 定时 Agent 工作流的模板,位于 agent-narrow-component-props.yml。
其运行机制非常巧妙:
- ⏰定时/手动模式:cron 每天触发(示例为
0 13 * * *),Agent 自动挑选一批高置信度的收窄改动,开 PR - 💬
/iterate迭代模式:维护者在 Agent PR 下评论/iterate+ 反馈意见,工作流会自动拉取 PR 上下文、评论与 Agent 记忆文件,把反馈合并进提示词继续迭代,实现"人反馈 → AI 修正"的闭环 - 📝标准化 PR 报告:每次运行按 response-template.md 输出结构化 PR 正文,包含变更表格、支撑收窄的真实调用方清单、验证结果和风险评级
其中 narrow-component-props-memory.md 是一个"Agent 记忆"文件,沉淀历次运行中维护者给出的长期约束(例如"只改apps/riptide-ui,不动apps/riptide-cloud"),让 AI 越跑越懂你的项目。
避坑清单:这些反模式千万别做
SKILL.md 结尾整理了一份"反模式"清单,建议收藏:
- ❌ 为了让 story 可以省略 handler,把回调改成可选
- ❌ 渲染一个调用
onAction?.(...)的菜单项 - ❌ 在真实代码是受控模式时,为 Storybook 加
default*props - ❌ 用
?? []、?? 0掩盖本应由调用方保证的必填状态 - ❌ 真实代码只用一种形态,却兼容多种 API 形状
配套的 Review Checklist 还有 10 项逐条核对项,例如"被删除的 prop 确实没有任何真实调用方使用""空值语义只保留给真实状态(如当前无焦点项)"。
写在最后
从onSelect?.()到onSelect(),表面上只是删掉一个?,背后却是完整的类型治理方法论:以真实代码路径为准绳、类型尽量严格、兜底逻辑随宽类型一起删除、测试反向适配契约。
这套技能对新手同样友好——你不需要先成为 TypeScript 类型专家,只要跟随 SKILL.md 的 11 步工作流,配合 marketplace.json 中声明的技能市场安装方式,就能让项目的 React 组件契约一天天变得更干净。仓库里还有 improve-claude-md、show-me、design-control-loop 等技能,值得一并探索。
【免费下载链接】skills项目地址: https://gitcode.com/GitHub_Trending/skills53/skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考