如何修复TypeScript DeepPartial工具类型的可选属性必填问题?
解决DeepPartial工具类型中原有可选属性被强制要求的问题
问题
我需要实现一个DeepPartial<Type, Property>工具类型,要求:
- 接收对象类型
Type作为泛型参数 - 接收字符串联合类型
Property作为指定属性 - 递归将所有匹配
Property的属性设为可选
但当前实现存在问题:原类型中已有的可选属性,在生成的类型中被错误地要求必须存在(仅能赋值undefined),而非保持原有的可选状态。
当前实现代码
export type DeepPartial<Type, Property> = Type extends Array<infer ArrayType> ? Array<DeepPartial<ArrayType, Property>> : Type extends Record<string, unknown> ? { [Key in Extract<keyof Type, Property>]?: DeepPartial<Type[Key], Property>; } & { [Key in Exclude<keyof Type, Property>]: DeepPartial<Type[Key], Property>; } : Type;
示例与问题复现
定义测试类型:
type Test = { a: boolean, b: { a: 1, b: { c?: string; }; }, c?: string; };
以下代码被TS判定为类型无效(本应合法):
const d: DeepPartial<Test, 'a'> = {b: {b: {}}};
必须显式给原有可选属性赋值undefined才能通过类型检查:
const d: DeepPartial<Test, 'a'> = {b: {b: {c: undefined}}, c: undefined};
问题原因
原实现中,Exclude<keyof Type, Property>会丢失原有属性的可选性信息。用该结果做映射类型时,所有非指定属性都会被强制设为必选,哪怕原属性是可选的。比如原类型中的c?: string,经过映射后变成了必选的string | undefined,因此必须显式赋值。
修复方案
通过Omit<Type, Property>获取非指定属性的类型(保留原可选性),再对其进行递归处理,同时合并指定属性的可选类型:
export type DeepPartial<Type, Property extends PropertyKey> = Type extends Array<infer ArrayType> ? Array<DeepPartial<ArrayType, Property>> : Type extends object ? // 将指定属性设为可选并递归处理类型 { [K in Extract<keyof Type, Property>]?: DeepPartial<Type[K], Property> } // 保留非指定属性的原可选性,同时递归处理类型 & { [K in keyof Omit<Type, Property>]: DeepPartial<Type[K], Property> } // 展开交叉类型,优化类型显示可读性 extends infer Merged ? { [K in keyof Merged]: Merged[K] } : never : Type;
验证
修复后的类型可以正确处理原有可选属性:
// 现在这个赋值完全合法 const d: DeepPartial<Test, 'a'> = {b: {b: {}}}; // 以下赋值也依然有效 const e: DeepPartial<Test, 'a'> = {a: true, b: {a: 1, b: {c: 'test'}}, c: 'hello'}; const f: DeepPartial<Test, 'a'> = {c: undefined};
内容的提问来源于stack exchange,提问作者Furman
相关产品推荐
相关产品推荐

