← 返回导航

PartialByKeys 按 key 设可选

工具类型 · 中级

场景说明

内置的 Partial<T> 会把所有属性都变成可选,但实际业务中我们经常只需要把"某些指定的 key"变成可选,其余保持不变。比如创建用户时 id、createdAt 是后端生成的不需要传,其余字段必填;更新用户时某些字段可选、某些字段必填。PartialByKeys 通过交叉类型 + Pick/Omit 的组合,精确控制哪些 key 变可选,粒度比 Partial 更细,是表单提交、DTO 设计中的高频工具类型。

完整代码


      
// PartialByKeys:按指定的 key 将对象属性设为可选,其余保持不变
// 比 Partial 更精细,常用于 DTO、表单提交、配置合并等场景

// 1. 实现方式一:Pick 可选部分 + Omit 保留部分,然后交叉
type PartialByKeys<T, K extends keyof T> =
  Partial<Pick<T, K>> & Omit<T, K>;

// 2. 实现方式二:映射类型 + 条件类型(更直观)
type PartialByKeysV2<T, K extends keyof T> = {
  [P in keyof T as P extends K ? P : P]?: T[P];
} & {
  [P in keyof T as P extends K ? never : P]: T[P];
};

// 3. 简化版:直接用映射类型 + 条件可选
type PartialByKeysV3<T, K extends keyof T = keyof T> = {
  [P in keyof T]?: P extends K ? T[P] : T[P];
} extends infer O
  ? { [P in keyof O]: O[P] }
  : never;

// 4. 推荐实现:交叉 + 展开合并(最简洁高效)
type Simplify<T> = { [K in keyof T]: T[K] };

type PartialByKeysFinal<T, K extends keyof T> =
  Simplify<Partial<Pick<T, K>> & Omit<T, K>>;

// 5. 业务示例:用户实体
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  avatar: string;
  createdAt: string;
  updatedAt: string;
}

// 6. 创建用户 DTO:id、createdAt、updatedAt 可选(后端生成)
type CreateUserDto = PartialByKeysFinal<User, 'id' | 'createdAt' | 'updatedAt'>;

// 7. 更新用户 DTO:只有 name/email/age/avatar 可选,id 必填
type UpdateUserDto = PartialByKeysFinal<User, 'name' | 'email' | 'age' | 'avatar' | 'createdAt' | 'updatedAt'>;

// 8. 使用示例
const createData: CreateUserDto = {
  name: 'Alice',
  email: 'alice@example.com',
  age: 25,
  avatar: 'avatar.png',
  // id、createdAt、updatedAt 可以不传
};

const updateData: UpdateUserDto = {
  id: 1,       // id 必填(不在 K 中)
  name: 'Bob',   // name 可选,可以只传部分字段
  // 其他字段都可选
};

关键点解析

使用示例与类型推导


      
// CreateUserDto 推导结果
type CreateResult = CreateUserDto;
// => {
//   id?: number | undefined;
//   createdAt?: string | undefined;
//   updatedAt?: string | undefined;
//   name: string;
//   email: string;
//   age: number;
//   avatar: string;
// }

// UpdateUserDto 推导结果
type UpdateResult = UpdateUserDto;
// => {
//   id: number;           // 必填(不在 K 中)
//   name?: string;        // 可选
//   email?: string;       // 可选
//   age?: number;         // 可选
//   avatar?: string;      // 可选
//   createdAt?: string;   // 可选
//   updatedAt?: string;   // 可选
// }

// 不传 K 等价于 Partial
type AllOptional = PartialByKeysFinal<User, keyof User>;
// => 所有属性都可选

// key 不存在会报错
// type Bad = PartialByKeysFinal<User, 'notExist'>;  // 错误

// 实战:表单提交,部分字段可空
interface FormData {
  username: string;
  password: string;
  confirmPassword: string;
  captcha: string;
  remember: boolean;
}
type LoginForm = PartialByKeysFinal<FormData, 'remember' | 'captcha'>;
// username、password 必填;remember、captcha 可选
在 Playground 中尝试