关于 narrow-react-prop-types
用于收紧 React 组件属性类型,使其匹配实际代码路径中的状态。它以生产调用点为契约依据,并相应调整依赖组件、测试、Storybook 故事和验证。
主要功能
- 01依据实际调用点推导契约
- 02判定属性为必需、可选或移除
- 03收紧公开组件与子组件类型
- 04移除不支持状态的回退逻辑
- 05同步调整故事与测试
适用场景
- 01收紧因 Storybook 或测试而放宽的属性
- 02移除始终可交互界面的可选处理器
- 03使共享组件变体符合生产用法
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ humanlayer/ skills --skill narrow-react-prop-types安装后在 Claude Code 中输入/narrow-react-prop-types确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/agent-narrow-component-props.ymlnarrow-component-props-memory.mdresponse-template.md详细介绍
收窄 React 属性类型
当 React 组件的属性因故事、模拟、测试或演示而被放宽,导致其类型现在表达了实际应用不会进入的状态时,请使用此技能。
目标是让组件类型描述真实生产代码路径的契约,然后要求故事、测试和模拟适配此契约,而不是削弱它。
核心要求
- 更改类型前,先找出所有非测试、非 Storybook 的调用位置。
- 将生产代码路径视为属性契约的事实来源。
- 不要仅仅因为可选属性能让 Storybook、测试或模拟数据更方便,就保留它们。
- 只有当存在非 Storybook、非测试的调用位置不提供这些属性,且有充分理由不提供时,才将属性保留为可选。
- 类型不应允许表达非测试、非 Storybook 调用位置中未观察到的状态。
- 类型应尽可能严格,从而让代码尽可能简单。
- 尽可能从现有生产代码路径中的值和 API 派生并提取类型。
工作流程
有关通过 CodeLayer 运行此技能的 GitHub Actions 工作流示例,请参阅 references/agent-narrow-component-props.yml。其示例 Agent 记忆文件为 references/narrow-component-props-memory.md。有关 CI Agent 响应格式,请参阅 references/response-template.md。
1. 找出可疑组件
寻找具有以下特征的组件:
- 属性接口庞大,且包含许多可选字段。
- 可选回调调用,例如
onSelect?.(...)或onArchive?.(...)。 - 回退状态处理,例如
items ?? []、count ?? 0,或对可能始终由生产代码提供的值使用handler && ...。 - 界面控件始终渲染,但对应的回调却是可选的。
- 看起来专为演示设计的属性,例如
defaultFoo、其他处理器形式,或生产代码未使用的显示开关。
不要仅根据故事或测试选择目标。只能将故事和测试作为类型已被放宽的佐证,不能将其视为某种状态真实存在的证据。
2. 找出所有实际使用位置
搜索该组件、导出的属性类型以及共享子组件基元的所有导入和使用位置。
根据调用位置属于生产代码路径还是辅助代码进行分类:
- 生产代码路径:应用路由、已接入的组件、提供程序、钩子、生产包导出,以及这些路径使用的共享组件。
- 辅助代码:Storybook 故事、测试文件、固定数据、模拟、演示框架以及仅用于视觉展示的示例。
只有生产代码路径应决定组件 API 支持的内容。
3. 从生产代码路径派生真实类型
阅读生产代码调用位置,并对每个属性进行分类:
- 必需:每个非测试、非 Storybook 的调用位置都会提供该属性。
- 可选:至少有一个非测试、非 Storybook 的调用位置省略该属性,且这一省略代表有意义的运行时状态。
- 移除:没有任何非测试、非 Storybook 的调用位置使用该属性。
可空性和可选性并不相同。如果生产代码始终传入某个属性,但其值可能为空,则优先使用必需的可空属性,例如 focusedItem: FocusedItem | null,而不是 focusedItem?: FocusedItem | null。
4. 收紧公开属性类型
更新导出的属性类型,使其仅与生产代码路径中观察到的状态相匹配。
类型越宽松、可选项越多,组件就必须考虑越多可能状态。每个可选属性都会增加一个分支,而组件必须处理、测试并确保其正确。优先使用严格类型来阻止不可能的状态,而不是依赖宽泛类型并添加防御式渲染逻辑。
如果组件始终渲染交互控件,就应要求提供使其正常工作的处理器。不要允许无效状态,例如可见菜单项调用 onRename?.(...)。
5. 尽可能推导和提取类型
优先从实际 API 推导类型,而不是手动重述:
Parameters<typeof fn>[0]用于函数参数类型。ReturnType<typeof fn>用于返回类型。Extract<Union, Shape>用于将联合类型缩小到实际变体。- 对 React 状态设置器,使用
React.Dispatch<React.SetStateAction<T>>,而不是将其近似为(value: T) => void。
如果类型推断会扩大或模糊预期的状态形状,请优先显式指定状态类型参数:
const [dialogState, setDialogState] = useState<DialogState>({
id: null,
isOpen: false,
})
避免依赖隐式的 useState(...) 类型推断,尤其是它会产生宽泛的可空对象形状、扩宽字符串字面量,或导致回调类型之后需要手动近似时。
6. 也要收紧内部子组件的 props
不要只处理导出的组件,还要检查它传给子组件原语的宽泛 props。
如果行、菜单或按钮子组件仅仅因为父组件 props 过于宽泛才接收可选处理程序,也要收紧这些内部 props。将这样的可选调用:
onRename?.(id, name)
改为必需调用:
onRename(id, name)
7. 移除不支持状态的回退逻辑
将 props 设为必需后,移除仅为适配扩宽类型而存在的防御性回退逻辑。
例如:
new Set(expandedIds ?? defaultExpandedIds ?? [])
应改为:
new Set(expandedIds)
items && items.length > 0
应改为:
items.length > 0
8. 更新共享该 prop 类型的所有变体
如果多个组件共享宽泛的 prop 类型,请一并更新,确保它们都遵循相同的实际代码路径契约。
9. 让测试和故事适配实际代码
如果缩小 props 范围后故事或测试报错,请通过提供符合实际情况的处理程序和状态来修复。不要为了减少测试设置而再次将实际代码路径中的 props 设为可选。
如果故事或测试设置过于冗长,请创建满足严格实际代码路径契约的测试辅助工具或夹具。将辅助工具放在支持代码中;不要弱化组件 API。
10. 验证更改
对更改所在的软件包,以及每个使用了该组件的实际应用或软件包,运行软件包级别的类型检查。
有仓库专用的验证命令时,请使用这些命令。在这个 monorepo 中,优先使用:
bun --bun run typecheck --filter <package>
11. 格式化响应
以 CI Agent 身份运行时,请按照 references/response-template.md 格式化最终响应。此响应将作为 PR 正文。
请包含:
- 收窄了多少个组件的摘要
- 附理由的更改表格
- 支持每项收窄的实际调用位置
- 需要更新的支持代码(故事/测试)
- 验证结果
- 风险评估
审查清单
- 更改后的 prop 类型是根据非测试、非 Storybook 的调用位置推导的。
- 对于始终渲染的交互,已移除可选回调。
- 已渲染的菜单项和按钮不会因缺少处理程序而失效。
- 实际代码路径未使用已移除的 props。
- 仅在确有实际状态时保留可空性,例如当前没有焦点。
- 尽可能推导或提取类型,而不是手动重复定义。
- 如果类型推断会扩大或模糊预期状态,请使用
useState<T>(...)。 - 共享变体均符合相同的收窄后契约。
- 共享软件包和使用它的实际应用或软件包均通过类型检查。
应避免的反模式
- 为了让故事可以省略回调而将回调设为可选。
- 渲染调用
onAction?.(...)的菜单项。 - 在实际代码受控的情况下,为 Storybook 添加
default*props。 - 使用
?? []或?? 0来掩盖缺失的必需实际状态。 - 在实际代码只使用一种 API 形状时,接受多种 API 形状。
- 将纯组件当作模拟组件,放宽其契约。