← 返回导航

RequiredByKeys 按 key 设必填

工具类型 · 中级

场景说明

内置的 Required<T> 会把所有属性都变成必填,但实际场景中常常只需要把"部分指定的 key"变成必填,其余保持原样。比如一个配置对象大部分字段有默认值可选,但少数关键字段(如 baseURL、apiKey)必须明确传入。RequiredByKeys 与 PartialByKeys 思路完全一致,只是用 Required 代替 Partial,精准控制哪些字段必填,是配置校验、函数参数约束中的常用工具类型。

完整代码


      
// RequiredByKeys:按指定的 key 将对象属性设为必填,其余保持不变
// 与 PartialByKeys 互为逆操作,Pick + Required + Omit 组合实现

// 1. 核心实现:Pick 必填部分 + Omit 保留部分,交叉后展平
type Simplify<T> = { [K in keyof T]: T[K] };

type RequiredByKeys<T, K extends keyof T = keyof T> =
  Simplify<Required<Pick<T, K>> & Omit<T, K>>;

// 2. 映射类型实现(等价写法)
type RequiredByKeysV2<T, K extends keyof T = keyof T> =
  Simplify<
    { [P in K]-?: T[P] } &
    { [P in Exclude<keyof T, K>]: T[P] }
  >;

// 3. 业务示例:SDK 配置
interface SdkConfig {
  apiKey?: string;
  baseURL?: string;
  timeout?: number;
  headers?: Record<string, string>;
  retry?: number;
  debug?: boolean;
  logLevel?: 'debug' | 'info' | 'warn' | 'error';
}

// 4. 初始化时必须传的配置:apiKey 和 baseURL 必填,其他可选
type InitConfig = RequiredByKeys<SdkConfig, 'apiKey' | 'baseURL'>;

// 5. 使用示例:SDK 初始化函数
class SdkClient {
  private config: Required<SdkConfig>;

  constructor(initConfig: InitConfig) {
    this.config = {
      timeout: 10000,
      headers: {},
      retry: 3,
      debug: false,
      logLevel: 'info',
      ...initConfig,  // 用户传入的覆盖默认值
    };
  }

  getConfig(): Required<SdkConfig> {
    return this.config;
  }
}

// 6. 另一个示例:表单数据
interface FormData {
  username?: string;
  password?: string;
  captcha?: string;
  remember?: boolean;
  redirectURL?: string;
}

// 登录提交时,用户名密码必填,其他可选
type LoginSubmitData = RequiredByKeys<FormData, 'username' | 'password'>;

// 使用
const client = new SdkClient({
  apiKey: 'abc123',   // 必填
  baseURL: 'https://api.example.com',  // 必填
  timeout: 5000,      // 可选
  debug: true,         // 可选
});

const loginData: LoginSubmitData = {
  username: 'admin',  // 必填
  password: '123456', // 必填
  captcha: 'abcd',   // 可选
};

关键点解析

使用示例与类型推导


      
// InitConfig 推导结果
type InitCfg = InitConfig;
// => {
//   apiKey: string;              // 必填(K 中)
//   baseURL: string;             // 必填(K 中)
//   timeout?: number | undefined;  // 可选(不在 K 中)
//   headers?: Record<string, string> | undefined;  // 可选
//   retry?: number | undefined;  // 可选
//   debug?: boolean | undefined; // 可选
//   logLevel?: 'debug' | 'info' | 'warn' | 'error' | undefined; // 可选
// }

// LoginSubmitData 推导结果
type LoginData = LoginSubmitData;
// => {
//   username: string;   // 必填
//   password: string;   // 必填
//   captcha?: string;   // 可选
//   remember?: boolean; // 可选
//   redirectURL?: string; // 可选
// }

// 必填字段缺失会报错
// const badConfig: InitConfig = { apiKey: 'key' };  // 错误:缺少 baseURL

// 不传 K 等价于 Required
type AllRequired = RequiredByKeys<SdkConfig>;
// => 所有属性都必填

// 实战:与 PartialByKeys 配合
type Mixed = RequiredByKeys<
  PartialByKeys<User, 'age' | 'avatar'>,
  'id' | 'name'
>;
// id、name 强制必填,age、avatar 强制可选,email 等保持原样

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  avatar: string;
}
在 Playground 中尝试