内置的 Required<T> 只能把对象的第一层属性变为必填,但实际业务中经常遇到多层嵌套对象。比如配置合并:默认配置是全必填,用户传入的配置是 DeepPartial,合并后需要确保所有字段都有值(DeepRequired)。DeepRequired 通过递归 + 条件类型,把嵌套对象的所有属性(含数组元素)都变为必填,与 DeepPartial 互为逆操作,是配置合并、默认值填充场景的标准工具类型。
// DeepRequired:递归将对象所有属性变为必填
// 与 DeepPartial 对应,支持对象、数组、函数等类型递归处理
// 1. 基础版:只处理纯对象
type DeepRequiredBasic<T> = {
[K in keyof T]-?: T[K] extends object
? DeepRequiredBasic<T[K]>
: T[K];
};
// 2. 完整版:区分对象、数组、函数、原始类型
type DeepRequired<T> =
T extends (...args: any[]) => any
? T
: T extends readonly any[]
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;
// 3. 测试用例:嵌套对象 + 数组 + 可选属性
interface UserConfig {
name?: string;
age?: number;
profile?: {
avatar?: string;
bio?: string;
contact?: {
email?: string;
phone?: string;
};
};
tags?: string[];
permissions?: {
roles?: string[];
resources?: {
id?: string;
actions?: string[];
}[];
};
onUpdate?: (config: Partial<UserConfig>) => void;
}
// 4. 深度必填后的类型
type FullUserConfig = DeepRequired<UserConfig>;
// 5. 配置合并函数:用户配置 + 默认配置 = 完整配置
function mergeConfig(
userConfig: Partial<UserConfig>,
defaultConfig: DeepRequired<UserConfig>,
): DeepRequired<UserConfig> {
return {
name: userConfig.name ?? defaultConfig.name,
age: userConfig.age ?? defaultConfig.age,
profile: {
avatar: userConfig.profile?.avatar ?? defaultConfig.profile.avatar,
bio: userConfig.profile?.bio ?? defaultConfig.profile.bio,
contact: {
email: userConfig.profile?.contact?.email ?? defaultConfig.profile.contact.email,
phone: userConfig.profile?.contact?.phone ?? defaultConfig.profile.contact.phone,
},
},
tags: userConfig.tags ?? defaultConfig.tags,
permissions: {
roles: userConfig.permissions?.roles ?? defaultConfig.permissions.roles,
resources: userConfig.permissions?.resources ?? defaultConfig.permissions.resources,
},
onUpdate: userConfig.onUpdate ?? defaultConfig.onUpdate,
};
}
// 6. 使用示例
const defaults: FullUserConfig = {
name: 'guest',
age: 0,
profile: {
avatar: 'default.png',
bio: '',
contact: { email: '', phone: '' },
},
tags: [],
permissions: {
roles: [],
resources: [],
},
onUpdate: () => {},
};
-? 移除可选修饰符:映射类型中用 -? 移除属性的可选标记(与 +? 或 ? 加可选对应),这是 Required 内置类型的核心机制,DeepRequired 把它递归应用到每一层。T extends readonly any[] 匹配,然后用映射类型遍历数组索引,对每个元素递归 DeepRequired,确保数组元素内部的可选字段也被填必填。// 第一层属性从可选变必填
type Name = FullUserConfig['name'];
// => string(不再是 string | undefined)
// 嵌套对象也变必填
type Profile = FullUserConfig['profile'];
// => { avatar: string; bio: string; contact: { email: string; phone: string } }
// 全部都是必填,无 ?
// 数组元素内部也递归必填
type Resource = FullUserConfig['permissions']['resources'][0];
// => { id: string; actions: string[] } (都必填)
// 函数类型保持原样
type OnUpdate = FullUserConfig['onUpdate'];
// => (config: Partial<UserConfig>) => void (未被递归)
// 对比内置 Required:只处理一层
type ShallowRequired = Required<UserConfig>;
type ShallowProfile = ShallowRequired['profile'];
// profile 本身变必填了,但 profile.avatar 还是可选的
// => { avatar?: string; bio?: string; contact?: { email?: string; phone?: string } }