题目
实现一个通用的RequiredByKeys<T, K>,它接收两个类型参数T和K。
K指定应设为必选的T的属性集。当没有提供K时,它就和普通的Required<T>一样使所有的属性成为必选的。
例如:
interface User {
name?: string
age?: number
address?: string
}
type UserRequiredName = RequiredByKeys<User, 'name'> // { name: string; age?: number; address?: string }
解答
我们先来理解一下Required<T>类型,定义如下
type Required<T> = {
[P in keyof T]-?: T[P]
};
使用了TypeScript内部的「映射类型」语法, { [P in K]: T }。P in K 表达式类型于JavaScript for...in语法,用于遍历type中的所有类型K,以及类型变量T,用于表示TypeScript中的任何类型。
还可以在映射过程中使用附加的修饰符只读和问号 (?)。通过添加加号(+)和减号(-)前缀来添加和删除相应的修饰符。如果没有添加前缀,则默认使用加号。
使用场景为,把所有的可选类型,转为必要类型
type User = {
name?:string;
password?:string;
address?:string;
phone?:string;
}
type RequiredUser = Required<User>
实现思路是我们只需要选择与K关联的属性,根据需要设置并生成新的对象类型,然后根据剩余的属性构建另一个对象类型,最后使用&算子转换以上两种对象类型组合成一个新的对象类型
interface User {
name?: string
age?: number
address?: string
}
type Merge<T> = {
[P in keyof T]: T[P]
}
type RequiredByKeys<T, K = keyof T> = Merge<
{
[P in keyof T as P extends K ? P : never]-?: T[P]
} & {
[P in keyof T as P extends K ? never : P]: T[P]
}
>
type UserRequiredName = RequiredByKeys<User, 'name'>
// { name: string; age?: number; address?: string }
使用as子句语法重新映射类型中的健
type MappedTypeWithNewKeys<T> = {
[K in keyof T as NewKeyType]:T[K]
}
其中NewKeyType的类型必须是string | number | symbol联合类型的子类型。在重新映射键的过程中,我们可以通过返回 never 类型来过滤键。
// 具体到 RequiredByKeys<User, 'name'>, 过滤得到{ name: string}
// never会过滤类型key
{
[ P in keyof User as P extends "name" ? P : never]-?: T[P]
}
// 具体到 RequiredByKeys<User, 'name'>, 过滤得到{ age?:number; address?:string }
{
[P in keyof User as P extends "name" ? never : P]: T[P]
}
甚至更简洁的解决方案如:
type RequiredByKeys<T, K = keyof T> = Merge<T & {
[ P in keyof T as P extends K ? P : never]-?: T[P]
}>
type UserRequiredName = RequiredByKeys<User, 'name'>
// type UserRequiredName = {
// name: string;
// age?: number | undefined;
// address?: string | undefined;
// }