deeppartial 是递归实现的深层可选类型,解决内置 partial 仅作用于第一层的问题;它通过条件类型判断属性是否为对象(排除 string/number/null 等),对对象类型递归应用 deeppartial,非对象则直接可选,并可扩展排除数组、date 等特殊对象。

用 TypeScript 实现深层属性可选,核心是写一个递归的 DeepPartial 工具类型。它不是简单加个 ?,而是对每个嵌套对象属性也递归应用 Partial,直到所有层级都变成可选。
为什么内置 Partial 不够用
TS 自带的 Partial<t></t> 只作用于第一层属性:
interface User {
name: string;
profile: {
age: number;
address: { city: string };
};
}
type Shallow = Partial<user>;
// 结果:{ name?: string; profile?: { age: number; address: { city: string } } }
// ❌ profile 和 address 里仍是必填,没递归</user>
手写 DeepPartial 的关键逻辑
需要两步判断:当前属性值是不是对象(object 且非 null),如果是,就递归处理;否则直接变可选。
- 用
extends object & {}安全判断是否为对象类型(排除string/number/null) - 用映射类型遍历
keyof T,对每个K做条件分支 - 用
DeepPartial<t></t>递归处理子类型
type DeepPartial<t> = {
[K in keyof T]?: T[K] extends object & {}
? DeepPartial<t>
: T[K];
};</t></t>
注意边界情况
数组、Date、RegExp 等也是 object,但通常不需要递归展开。如果项目中明确要跳过它们,可加额外排除:
type DeepPartial<t> = {
[K in keyof T]?: T[K] extends (object & {})
? T[K] extends any[] | Date | RegExp | Error
? T[K]
: DeepPartial<t>
: T[K];
};</t></t>
配合其他工具组合使用
实际开发中常和 Pick 或 Omit 联用。比如只让 user.info 下的字段深度可选:
type UserInfoPartial = DeepPartial<pick>>;</pick>
或先剔除某些字段再深度处理:
type SafePartial = DeepPartial<omit>></omit>










