narrow-react-prop-types
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNarrow React Prop Types
缩小React Prop类型范围
Use this skill when a React component's props have been widened for stories, mocks, tests, or demos and now express states the live application does not enter.
The goal is to make component types describe the real live-code-path contract, then require stories/tests/mocks to adapt to that contract instead of weakening it.
当React组件的Props为了stories、mock、测试或演示而被扩大范围,导致其包含了实际应用中不会出现的状态时,可以使用此技巧。
我们的目标是让组件类型准确描述实际代码路径的契约,进而要求stories/测试/mock去适配这个契约,而非弱化契约本身。
Core Requirements
核心要求
- Find the actual non-test, non-Storybook call sites before changing types.
- Treat live code paths as the source of truth for the prop contract.
- Do not preserve optional props only because they make Storybook, tests, or mock data easier.
- Keep props optional only when there are non-Storybook, non-test call sites which do not provide them and which have a good reason for not doing so.
- Types should not enable expressing states which are not observed in non-test, non-Storybook call sites.
- Types should be as strict as possible so code can be as simple as possible.
- Prefer deriving and extracting types from existing live-code-path values and APIs where possible.
- 在修改类型之前,先找到所有非测试、非Storybook的实际调用位置。
- 将实际代码路径视为Prop契约的唯一来源。
- 不要仅因为方便Storybook、测试或mock数据就保留可选Props。
- 仅当存在非Storybook、非测试的调用位置有合理理由不提供该Prop时,才将其设为可选。
- 类型不应允许表达非测试、非Storybook调用位置中未出现的状态。
- 类型应尽可能严格,从而让代码尽可能简洁。
- 尽可能从现有实际代码路径的取值和API中派生、提取类型。
Workflow
工作流程
For an example recurring GitHub Actions workflow that runs this skill through CodeLayer, see . Its example agent memory file is . For CI agent response formatting, see .
references/agent-narrow-component-props.ymlreferences/narrow-component-props-memory.mdreferences/response-template.md若要了解通过CodeLayer运行此技巧的周期性GitHub Actions示例工作流,请查看。其示例Agent内存文件为。CI Agent的响应格式请参考。
references/agent-narrow-component-props.ymlreferences/narrow-component-props-memory.mdreferences/response-template.md1. Identify the suspect component
1. 识别可疑组件
Look for components with these signals:
- Large props interfaces with many optional fields.
- Optional callback calls such as or
onSelect?.(...).onArchive?.(...) - Fallback state handling such as ,
items ?? [], orcount ?? 0around values live code likely always supplies.handler && ... - UI affordances that always render even though their callbacks are optional.
- Props that look demo-oriented, such as , alternate handler shapes, or display toggles not used by live code.
defaultFoo
Do not pick a target from a story or test alone. Use stories/tests only as supporting evidence that the type has been widened, not as evidence that a state is real.
寻找带有以下特征的组件:
- 包含大量可选字段的大型Props接口。
- 可选回调调用,例如或
onSelect?.(...)。onArchive?.(...) - 兜底状态处理,例如、
items ?? [],或围绕实际代码可能始终提供的值的count ?? 0逻辑。handler && ... - 即使回调是可选的,仍始终渲染的UI交互元素。
- 面向演示的Props,例如、备用处理函数形状,或实际代码未使用的显示切换开关。
defaultFoo
不要仅从story或测试中选择目标组件。仅将stories/测试作为类型被扩大的佐证,而非某状态真实存在的依据。
2. Find every live usage
2. 查找所有实际使用场景
Search for all imports/usages of the component, exported prop type, and shared child primitives.
Classify call sites by whether they are live code paths or support code:
- Live code paths: app routes, wired components, providers, hooks, production package exports, and shared components used by those paths.
- Support code: Storybook stories, test files, fixtures, mocks, demo harnesses, and visual-only examples.
Only live code paths should determine what the component API supports.
搜索组件、导出的Prop类型以及共享子原语的所有导入/使用位置。
将调用位置分为实际代码路径和支持代码两类:
- 实际代码路径:应用路由、已关联的组件、提供者、hooks、生产包导出,以及被这些路径使用的共享组件。
- 支持代码:Storybook stories、测试文件、测试数据、mock、演示工具,以及仅用于展示的示例。
只有实际代码路径才能决定组件API应支持的内容。
3. Derive the real types from the live code paths
3. 从实际代码路径推导真实类型
Read the live call sites and classify each prop:
- Required: every non-test, non-Storybook call site supplies it.
- Optional: at least one non-test, non-Storybook call site omits it and that omission is a meaningful runtime state.
- Removed: no non-test, non-Storybook call site uses it.
Nullability and optionality are different. If live code always passes a prop but the value can be empty, prefer a required nullable prop such as over .
focusedItem: FocusedItem | nullfocusedItem?: FocusedItem | null查看实际调用位置,对每个Prop进行分类:
- 必填:所有非测试、非Storybook的调用位置都提供该Prop。
- 可选:至少有一个非测试、非Storybook的调用位置省略了该Prop,且这种省略是有意义的运行时状态。
- 移除:没有非测试、非Storybook的调用位置使用该Prop。
可空性和可选性是不同的。如果实际代码始终传递某个Prop,但取值可能为空,优先使用必填可空Prop,例如,而非。
focusedItem: FocusedItem | nullfocusedItem?: FocusedItem | null4. Tighten the public prop type
4. 收紧公共Prop类型
Update exported prop types to match only the states observed in live code paths.
The looser and more optional a type is, the more possible states the component has to reason about. Every optional prop creates another branch the component must handle, test, and keep correct. Prefer strict types that prevent impossible states instead of broad types that require defensive render logic.
If the component always renders an interactive affordance, require the handler that makes it work. Do not allow inert states like a visible menu item that calls .
onRename?.(...)更新导出的Prop类型,使其仅匹配实际代码路径中观察到的状态。
类型越宽松、可选字段越多,组件需要考虑的可能状态就越多。每个可选Prop都会增加一个组件必须处理、测试并保持正确的分支。优先使用严格类型来避免不可能的状态,而非使用宽泛类型来编写防御性渲染逻辑。
如果组件始终渲染某个交互元素,则要求提供使其生效的处理函数。不允许出现可见菜单项却调用这种无响应的状态。
onRename?.(...)5. Derive and extract types where possible
5. 尽可能派生和提取类型
Prefer deriving types from the live APIs instead of restating them manually:
- for function argument types.
Parameters<typeof fn>[0] - for return types.
ReturnType<typeof fn> - for narrowing a union to a real variant.
Extract<Union, Shape> - for React state setters instead of approximating them as
React.Dispatch<React.SetStateAction<T>>.(value: T) => void
Prefer explicit state type parameters when inference would widen or obscure the intended state shape:
ts
const [dialogState, setDialogState] = useState<DialogState>({
id: null,
isOpen: false,
})Avoid relying on implicit inference when it produces broad nullable object shapes, string literal widening, or callback types that later need hand-written approximations.
useState(...)优先从实际API中派生类型,而非手动重写:
- 使用获取函数参数类型。
Parameters<typeof fn>[0] - 使用获取返回类型。
ReturnType<typeof fn> - 使用将联合类型缩小为实际存在的变体。
Extract<Union, Shape> - 使用表示React状态设置器,而非近似为
React.Dispatch<React.SetStateAction<T>>。(value: T) => void
当类型推断会扩大或模糊预期状态形状时,优先使用显式的状态类型参数:
ts
const [dialogState, setDialogState] = useState<DialogState>({
id: null,
isOpen: false,
})避免依赖隐式的推断,因为它可能产生宽泛的可空对象形状、字符串字面量扩大,或后续需要手动近似的回调类型。
useState(...)6. Tighten internal child props too
6. 同时收紧内部子组件的Props
Do not stop at the exported component if it passes broad props into child primitives.
If row/menu/button child components receive optional handlers only because the parent props were broad, tighten those internal props too. Replace optional calls like this:
ts
onRename?.(id, name)with required calls:
ts
onRename(id, name)不要仅停留在导出组件层面,如果它向子原语传递了宽泛的Props,也要一并收紧。
如果行/菜单/按钮子组件接收可选处理函数仅仅是因为父组件的Props被扩大了,那么也要收紧这些内部Props。将如下可选调用:
ts
onRename?.(id, name)替换为必填调用:
ts
onRename(id, name)7. Remove fallback logic for unsupported states
7. 移除针对不支持状态的兜底逻辑
Once props are required, remove defensive fallbacks that only existed for widened types.
Examples:
ts
new Set(expandedIds ?? defaultExpandedIds ?? [])should become:
ts
new Set(expandedIds)ts
items && items.length > 0should become:
ts
items.length > 0一旦Props设为必填,移除仅为扩大后的类型而存在的防御性兜底逻辑。
示例:
ts
new Set(expandedIds ?? defaultExpandedIds ?? [])应改为:
ts
new Set(expandedIds)ts
items && items.length > 0应改为:
ts
items.length > 08. Update all variants that share the prop type
8. 更新所有共享该Prop类型的变体
If multiple components share the broad prop type, update them together so they all enforce the same live-code-path contract.
如果多个组件共享同一个宽泛的Prop类型,要一并更新它们,确保它们都遵循相同的实际代码路径契约。
9. Let tests and stories adapt to live code
9. 让测试和stories适配实际代码
If a story or test breaks after narrowing props, fix it by providing realistic handlers and state. Do not make live-code-path props optional again to reduce test setup.
If the story/test setup feels verbose, create a test helper or fixture that satisfies the strict live-code-path contract. Keep the helper in support code; do not weaken the component API.
如果缩小Props范围后导致story或测试失败,通过提供真实的处理函数和状态来修复它。不要为了简化测试设置而再次将实际代码路径的Props设为可选。
如果story/测试的设置过程过于繁琐,可以创建一个满足严格实际代码路径契约的测试助手或测试数据。将助手放在支持代码中;不要弱化组件API。
10. Validate the change
10. 验证变更
Run package-level typechecks for the changed package and each live app/package that consumes the changed component.
Use repository-specific validation commands when available. In this monorepo, prefer:
bash
bun --bun run typecheck --filter <package>对修改后的包以及所有使用该修改组件的实际应用/包运行包级类型检查。
使用仓库特定的验证命令(如果有)。在此单体仓库中,优先使用:
bash
bun --bun run typecheck --filter <package>11. Format the response
11. 格式化响应
When running as a CI agent, format your final response according to . This response becomes the PR body.
references/response-template.mdInclude:
- Summary of how many components were narrowed
- Table of changes with rationale
- Live call sites that justify each narrowing
- Support code (stories/tests) that needed updating
- Validation results
- Risk assessment
作为CI Agent运行时,请根据格式化最终响应。此响应将作为PR的正文内容。
references/response-template.md响应应包含:
- 已缩小范围的组件数量摘要
- 包含理由的变更表格
- 证明每次缩小合理性的实际调用位置
- 需要更新的支持代码(stories/测试)
- 验证结果
- 风险评估
Review Checklist
审查检查清单
- The changed prop type was derived from non-test, non-Storybook call sites.
- Optional callbacks are removed for always-rendered interactions.
- Rendered menu items and buttons cannot be inert because of missing handlers.
- Removed props are not used by live code paths.
- Nullability is preserved only for real states, such as no current focus.
- Types are derived or extracted where possible rather than manually duplicated.
- is used where inference would otherwise widen or obscure the intended state.
useState<T>(...) - Shared variants compile against the same narrowed contract.
- Typecheck passes for the shared package and consuming live app/package.
- 修改后的Prop类型是从非测试、非Storybook的调用位置派生而来。
- 始终渲染的交互元素的可选回调已被移除。
- 渲染的菜单项和按钮不会因缺少处理函数而无响应。
- 已移除的Props未被实际代码路径使用。
- 仅为真实状态保留可空性,例如无当前焦点的情况。
- 尽可能派生或提取类型,而非手动重复定义。
- 在类型推断会扩大或模糊预期状态的场景下使用了。
useState<T>(...) - 共享变体针对同一缩小后的契约编译通过。
- 共享包和使用该组件的实际应用/包的类型检查已通过。
Anti-Patterns to Avoid
需避免的反模式
- Making callbacks optional so stories can omit them.
- Rendering a menu item that calls .
onAction?.(...) - Adding props for Storybook when live code is controlled.
default* - Using or
?? []to hide missing required live state.?? 0 - Accepting multiple API shapes when live code only uses one.
- Treating pure components as mock components with relaxed contracts.
- 为了让stories可以省略回调函数而将其设为可选。
- 渲染调用的菜单项。
onAction?.(...) - 当实际代码为受控模式时,为Storybook添加Props。
default* - 使用或
?? []来掩盖缺失的必填实际状态。?? 0 - 当实际代码仅使用一种API形状时,却接受多种API形状。
- 将纯组件视为具有宽松契约的mock组件。