如何基于可选kind属性缩小嵌套对象的联合类型范围?
问题分析与解决方案
你遇到的核心问题是:TLabeledField 类型没有根据可选的 kind 属性正确收窄联合类型,导致指定 kind: "number" 时传入不属于该类型的 block 属性不会触发类型错误。
原方案中使用 PartialBy 直接将 kind 设为可选,会把联合类型每个分支的 kind 都变成可选,最终合并后的类型会保留所有分支的属性,无法实现基于 kind 的类型校验。下面是修正后的实现:
类型重构方案
首先拆分类型结构,明确公共字段与各 kind 专属属性的映射关系,再重新定义支持默认 kind: "string" 的 TLabeledField:
// 提取公共字段 type TFieldCommon = { placeholder?: string; required?: boolean; }; // 定义各kind对应的专属属性映射 type TFieldKindMap = { string: { value?: string; block?: boolean }; password: { value?: string }; email: { value?: string }; number: { value?: number }; boolean: { value?: boolean; inline?: boolean; toggle?: boolean }; }; // 原始TField类型(保持原有定义) type TField = { field: string; name: string; } & TFieldCommon & { [K in keyof TFieldKindMap]: { kind: K } & TFieldKindMap[K]; }[keyof TFieldKindMap]; // 基础可选字段:name、field设为可选 type TLabeledFieldBase = TFieldCommon & Partial<Pick<TField, "name" | "field">>; // 指定kind时的类型:绑定对应kind的专属属性 type TLabeledFieldWithKind<K extends keyof TFieldKindMap> = TLabeledFieldBase & { kind: K; } & TFieldKindMap[K]; // 默认类型:不指定kind时,默认对应string类型的属性 type TLabeledFieldDefault = TLabeledFieldBase & Partial<{ kind: "string" }> & TFieldKindMap["string"]; // 合并所有可能的类型 type TLabeledField = TLabeledFieldDefault | { [K in Exclude<keyof TFieldKindMap, "string">]: TLabeledFieldWithKind<K>; }[Exclude<keyof TFieldKindMap, "string">]; // 保持原有集群类型定义 type TFieldCluster = { [key: string]: TLabeledField; };
验证效果
将重构后的类型应用到原函数中,即可实现预期的类型校验:
function makeFields(cluster: TFieldCluster) { const fieldMap = Object.entries(cluster).map(([str, path]) => { let label = str; let name, field; let required = false; if (label.endsWith("*")) { required = true; label = label.slice(0, -1); } if (label.startsWith("$")) { field = label.slice(1); name = label.replace(/([A-Z])/g, " $1").toLowerCase(); } else { field = label; name = label; } return [ field, { type: "string", name, field, required, ...path, }, ]; }); return Object.fromEntries(fieldMap); } const fields = makeFields({ title: {}, // ✅ 合法,默认kind为string subtitle: { kind: "boolean", toggle: true }, // ✅ 合法,boolean类型支持toggle about: { block: true }, // ✅ 合法,默认string类型支持block count: { kind: "number", block: true }, // ❌ 报错:number类型不存在block属性 });
核心改进点
- 拆分公共字段与专属属性,避免联合类型合并后属性混乱
- 通过显式的类型绑定,确保指定
kind时只能传入对应类型的属性 - 默认分支明确绑定
string类型的属性,实现kind不指定时的默认行为
内容的提问来源于stack exchange,提问作者Julian Brooks
相关产品推荐
相关产品推荐

