在 objectui#3161(objectstack#4115 台账批次 7)把 @object-ui/fields 的 FieldWidgetProps 改名为 FieldWidgetComponentProps 时发现的,未在该 PR 修复(改名不改行为)。
现象
packages/fields/src/widgets/types.ts:
export type FieldWidgetComponentProps< T = any > = {
value: T;
onChange: (val: T) => void;
field: FieldMetadata;
readonly?: boolean;
disabled?: boolean;
className?: string;
errorMessage?: string;
onUploadingChange?: (uploading: boolean) => void;
[key: string]: any; // ← 这一行
};
这个索引签名是 objectstack#4075 的机制:一个声称拥有全部键的类型,永远不可能被报告"缺了某个键"。具体后果:
props.required、props.error(spec 的 FieldWidgetPropsSchema 声明了这两个键,本地没有)都是合法读取,类型是 any,运行时恒为 undefined;
- 任何 widget 拼错 prop 名(
readOnly vs readonly、onchange)都能通过编译;
- 任何结构比较(parity 测试)对这个类型都是原理上无效的——批次 7 的守卫是唯一能看见这类问题的东西。
为什么没在 #3161 里顺手删
fields 包内约 48 个 widget 用 ...props 透传,删掉索引签名会在这些调用点产生真实的类型错误,需要逐个判断该 prop 到底该不该存在——这是行为/契约变更,不是符号改名。
建议做法
- 把真正需要的透传键显式声明(
id、name、aria-*、data-testid 等),或改为 & React.HTMLAttributes< HTMLElement > 之类有约束的交集;
- 逐 widget 修掉暴露出来的错误;
- 删掉
packages/fields/src/__tests__/spec-symbol-batch7.test.ts 里三条专门为此写的、会在删除索引签名时报红的钉扎(_IndexSignatureStillThere / _RequiredSilentlyReadsAsAny / _ErrorSilentlyReadsAsAny)。
关联:objectstack#4115、objectstack#4075、objectui#3161。
在 objectui#3161(objectstack#4115 台账批次 7)把
@object-ui/fields的FieldWidgetProps改名为FieldWidgetComponentProps时发现的,未在该 PR 修复(改名不改行为)。现象
packages/fields/src/widgets/types.ts:这个索引签名是 objectstack#4075 的机制:一个声称拥有全部键的类型,永远不可能被报告"缺了某个键"。具体后果:
props.required、props.error(spec 的FieldWidgetPropsSchema声明了这两个键,本地没有)都是合法读取,类型是any,运行时恒为undefined;readOnlyvsreadonly、onchange)都能通过编译;为什么没在 #3161 里顺手删
fields包内约 48 个 widget 用...props透传,删掉索引签名会在这些调用点产生真实的类型错误,需要逐个判断该 prop 到底该不该存在——这是行为/契约变更,不是符号改名。建议做法
id、name、aria-*、data-testid等),或改为& React.HTMLAttributes< HTMLElement >之类有约束的交集;packages/fields/src/__tests__/spec-symbol-batch7.test.ts里三条专门为此写的、会在删除索引签名时报红的钉扎(_IndexSignatureStillThere/_RequiredSilentlyReadsAsAny/_ErrorSilentlyReadsAsAny)。关联:objectstack#4115、objectstack#4075、objectui#3161。