← 返回导航

DeepRequired 深度必填

工具类型 · 中级

场景说明

内置的 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: () => {},
};

关键点解析

使用示例与类型推导


      
// 第一层属性从可选变必填
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 } }
在 Playground 中尝试