如何实现支持排除指定属性的TypeScript至少一个属性必填工具类型
TypeScript 实现支持排除属性的「至少一个必填」工具类型
问题描述
现有全可选属性的TypeScript接口MyInterface,已通过CustomRequired工具类型实现「至少一个属性必填」的约束。扩展得到NewInterface并新增可选属性property4后,需要实现新约束:必须包含property1/property2/property3中的至少一个,property4可选但无法满足必填要求。希望修改或扩展CustomRequired,使其支持传入排除属性的联合类型,同时保留原有无需排除参数的使用方式。
背景
原接口与工具类型实现:
interface MyInterface { property1?: string; property2?: string; property3?: string; // 注:原拼写proeprty3修正为property3 } // 原实现:要求至少一个属性必填 type AtLeastOnePropRequired<T> = { [P in keyof T]-?: Record<P, T[P]> }[keyof T]; type CustomRequired<T> = T & AtLeastOnePropRequired<T>;
使用示例:
// 要求property1/property2/property3中至少一个必填 CustomRequired<MyInterface>;
需求场景
扩展接口后:
interface NewInterface extends MyInterface { property4?: boolean; }
原CustomRequired<NewInterface>会将property4也纳入必填判断范围,不符合需求。需要让property4仅作为可选属性,不参与「至少一个必填」的校验。
解决方案
修改CustomRequired,添加可选的排除属性泛型参数,默认值设为never(表示不排除任何属性)。核心逻辑是先排除指定属性,再对剩余属性应用「至少一个必填」约束,最后与原类型交叉:
type CustomRequired<T, Excluded extends keyof T = never> = T & ( // 筛选出需要强制至少一个必填的属性(排除指定属性) { [P in Exclude<keyof T, Excluded>]-?: Record<P, T[P]> }[Exclude<keyof T, Excluded>] );
类型逻辑说明
Exclude<keyof T, Excluded>:从T的属性键中移除需要排除的属性,得到需要参与「至少一个必填」校验的属性集合。{ [P in ...]-?: Record<P, T[P]> }:为每个待校验属性生成一个仅该属性必填的类型。[...]Exclude<keyof T, Excluded>]:取上述所有类型的联合,确保至少有一个待校验属性必填。- 与原类型
T交叉:保留所有属性的可选性,同时强制待校验属性中至少一个存在。
示例验证
场景1:无排除属性(原有逻辑)
// 要求NewInterface中任意属性至少一个必填 type Case1 = CustomRequired<NewInterface>; // 合法:包含property4 const validCase1: Case1 = { property4: true }; // 非法:无任何属性 const invalidCase1: Case1 = {}; // 报错
场景2:排除property4
// 要求必须包含property1/property2/property3中的至少一个,property4可选 type Case2 = CustomRequired<NewInterface, 'property4'>; // 合法:包含property2和property4 const validCase2: Case2 = { property2: "test", property4: false }; // 非法:仅包含property4 const invalidCase2: Case2 = { property4: false }; // 报错
场景3:排除多个属性
// 要求必须包含property2/property3中的至少一个,排除property1和property4 type Case3 = CustomRequired<NewInterface, 'property1' | 'property4'>; // 合法:包含property3 const validCase3: Case3 = { property3: "demo" }; // 非法:仅包含property1和property4 const invalidCase3: Case3 = { property1: "hi", property4: true }; // 报错
替代方案(不修改原CustomRequired)
若希望保留原CustomRequired,可单独创建新工具类型:
type CustomRequiredExclude<T, Excluded extends keyof T> = T & AtLeastOnePropRequired<Omit<T, Excluded>>;
内容的提问来源于stack exchange,提问作者pez
相关产品推荐
相关产品推荐

