内置的 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', // 可选
};
Required<Pick<T, K>> & Omit<T, K>,先挑出要变必填的字段用 Required 处理,再与剩下的字段交叉,思路清晰且复用内置工具类型。-? 修饰符,它会移除属性上的可选标记,与 Partial 的 +?(即 ?)互为逆操作。A & B,用 Simplify 重新映射后展平成一个对象,IDE 提示更友好,是工具类型的常用辅助技巧。extends keyof T 约束,传错 key 编译期报错。// 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;
}