mcpField
N
narrow-react-prop-types

narrow-react-prop-types

github.com/humanlayer开发5.5k1.3k 次安装3 周前更新
代码仓库

关于 narrow-react-prop-types

用于收紧 React 组件属性类型,使其匹配实际代码路径中的状态。它以生产调用点为契约依据,并相应调整依赖组件、测试、Storybook 故事和验证。

主要功能

  1. 01依据实际调用点推导契约
  2. 02判定属性为必需、可选或移除
  3. 03收紧公开组件与子组件类型
  4. 04移除不支持状态的回退逻辑
  5. 05同步调整故事与测试

适用场景

  1. 01收紧因 Storybook 或测试而放宽的属性
  2. 02移除始终可交互界面的可选处理器
  3. 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.yml
narrow-component-props-memory.md
response-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 形状。
  • 将纯组件当作模拟组件,放宽其契约。

评论

支持 Markdown