本文对 11 个Ts 类型体操进行专项训练,喜欢的朋友可以收藏起来今后翻看。
1. 实现一个通用的 RequiredByKeys
实现一个通用的 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 }
自写答案
type RequiredByKeys<T extends {}, K extends keyof T, U ={
[P in keyof T as P extends K ? P : never]-?: T[P];
} & {
[P in keyof T as P extends K ? never : P]: T[P];
}> = {
[A in keyof U]: U[A]
}
上面的答案比下面这个好一些:
type RequiredByKeys<T extends {}, K extends keyof T> = {
[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 RequiredByKeys<
T extends object,
K extends keyof T | string = keyof T,
H = {
[P in K extends keyof T ? K : never]: Exclude<T[P], null | undefined>;
} & { [P in Exclude<keyof T, K>]?: T[P] }
> = { [P in keyof H]: H[P] };
2. 实现一个通用的 PartialByKeys
实现一个通用的PartialByKeys<T, K>
,它接收两个类型参数 T 和 K。
K 指定应设置为可选的 T 的属性集。当没有提供 K 时,它就和普通的Partial<T>
一样使所有属性都是可选的。
例如:
interface User {
name: string;
age: number;
address: string;
}
type UserPartialName = PartialByKeys<User, "name">; // { name?:string; age:number; address:string }
自写答案
type PartialByKeys2<T extends {}, K=unknown, U = {
[P in keyof T as P extends K ? P : never]?: T[P]
} & {
[P in keyof T as P extends K ? never : P]: T[P]
}> = K extends unknown ? {
[P in keyof U]: U[P]
} : {
[P in keyof T]?: T[P]
}
给泛型默认值,一般是 unknown 就可以规避反省数目对不上的报错。
答案
type PartialByKeys<
T,
K extends keyof T | string = keyof T,
H = { [P in Exclude<keyof T, K>]: T[P] } & {
[P in K extends keyof T ? K : never]?: T[P];
}
> = { [K in keyof H as [H[K]] extends [never] ? never : K]: H[K] };
3. 实现 EndsWith
实现EndsWith<T, U>
,接收两个 string 类型参数,然后判断 T 是否以 U 结尾,根据结果返回 true 或 false
例如:
type a = EndsWith<"abc", "bc">; // expected to be false
type b = EndsWith<"abc", "abc">; // expected to be true
type c = EndsWith<"abc", "d">; // expected to be false
自写答案
type EndsWith<
A extends string,
B extends string
> = A extends `${infer F}${infer R}`
? B extends A
? true
: EndsWith<R, B>
: false;
答案
type EndsWith<T extends string, U extends string> = T extends `${any}${U}`
? true
: false;
模板字符串中并不是只能一个一个的比对的,使用 ${any}
可以一次越过好几个。
4. 实现 StartsWith
实现StartsWith<T, U>
,接收两个 string 类型参数,然后判断 T 是否以 U 开头,根据结果返回 true 或 false
例如:
type a = StartsWith<"abc", "ac">; // expected to be false
type b = StartsWith<"abc", "ab">; // expected to be true
type c = StartsWith<"abc", "abcd">; // expected to be false
自写答案
type StartsWith<A extends string, B extends string> = A extends `${B}${any}` ? true : false;
5. 实现 PickByType
找出 T 中类型为 U 的属性
示例:
type OnlyBoolean = PickByType<
{
name: string;
count: number;
isReadonly: boolean;
isEnable: boolean;
},
boolean
>; // { isReadonly: boolean; isEnable: boolean; }
自写答案
type PickByType<T extends {}, K = unknown> = {
[P in keyof T as T[P] extends K ? P : never]: T[P]
}
6. 实现 RemoveIndexSignature
实现 RemoveIndexSignature<T>
, 将索引字段从对象中排除掉.
示例:
type Foo = {
[key: string]: any;
foo(): void;
};
type A = RemoveIndexSignature<Foo>; // expected { foo(): void }
答案
type RemoveIndexSignature<T extends object> = {
[P in keyof T as string extends P
? never
: number extends P
? never
: symbol extends P
? never
: P]: T[P];
};
写不出来,但是索引序列指的并不是 'name' 这种,上面的方法是专门用来处理索引序列的,背下即可。
7. 实现 MinusOne
给定一个正整数作为类型的参数,要求返回的类型是该数字减 1。
例如:
type Zero = MinusOne<1>; // 0
type FiftyFour = MinusOne<55>; // 54
自写答案
type BuildArr<T extends number, K extends any[] = []> = K["length"] extends T ? K : BuildArr<T, [...K, unknown]>;;
type MinusOne<T extends number> = BuildArr<T> extends [infer F, ...infer R] ? R["length"] : 0;
8. 实现 DropChar
从字符串中剔除指定字符。
例如:
type Butterfly = DropChar<" b u t t e r f l y ! ", " ">; // 'butterfly!'
自写答案
type DropChar<A extends string, B extends string> = A extends `${B}${infer R}` ? (
DropChar<R, B>
) : (
A extends `${infer F}${infer R2}` ? `${F}${DropChar<R2, B>}` : A
);
答案
type DropChar<
S extends string,
C extends string
> = S extends `${infer L}${infer R}`
? `${C extends L ? '' : L}${DropChar<R, C>}`
: S;
这个答案没有我写的好,我的可以是多个字符。
9. 实现类型 PercentageParser
实现类型 PercentageParser。根据规则 /^(\+|\-)?(\d*)?(\%)?$/
匹配类型 T。
匹配的结果由三部分组成,分别是:[正负号, 数字, 单位],如果没有匹配,则默认是空字符串。
例如:
type PString1 = "";
type PString2 = "+85%";
type PString3 = "-85%";
type PString4 = "85%";
type PString5 = "85";
type R1 = PercentageParser<PString1>; // expected ['', '', '']
type R2 = PercentageParser<PString2>; // expected ["+", "85", "%"]
type R3 = PercentageParser<PString3>; // expected ["-", "85", "%"]
type R4 = PercentageParser<PString4>; // expected ["", "85", "%"]
type R5 = PercentageParser<PString5>; // expected ["", "85", ""]
自写答案
type PercentageParser<T extends string, Re extends string[] = []> = Re["length"] extends 0 ? (T extends `${'+'}${infer R}` ? PercentageParser<R, ['+']> : (
T extends `${'-'}${infer R}` ? PercentageParser<R, ['-']> : PercentageParser<T, ['']>
)):(
T extends `${infer R}${'%'}` ? [...Re, R, '%'] : (
T extends "" ? [...Re, "", ""] : [...Re, T, ""]
)
)
答案
type PercentageParser<
A extends string,
Sign extends '+' | '-' | '' = '',
Mark extends '' | '%' = ''
> = A extends `${infer L extends '+' | '-'}${infer R}`
? PercentageParser<R, L>
: A extends `${infer L}%`
? PercentageParser<L, Sign, '%'>
: [Sign, A, Mark];
如果能像答案那样将 + - 合起来处理就会节省很多代码。
10. 实现一个 IsUnion 类型
实现一个 IsUnion 类型, 接受输入类型 T, 并返回 T 是否为联合类型.
例如:
type case1 = IsUnion<string>; // false
type case2 = IsUnion<string | number>; // true
type case3 = IsUnion<[string | number]>; // false
自写答案
type IsUnion<T, K = T> = K extends K ? (
Exclude<T, K> extends never ? false: true
) : never
这里我们使用了让联合类型缩减的技巧。
11. 实现一个 IsNever 类型
实现一个以 T 作为泛型参数的 IsNever类型. 如果 T 是never, 返回 true, 否则返回 false.
示例:
type A = IsNever<never>; // expected to be true
type B = IsNever<undefined>; // expected to be false
type C = IsNever<null>; // expected to be false
type D = IsNever<[]>; // expected to be false
type E = IsNever<number>; // expected to be false
自写答案
type IsNever<T> = (T extends never ? true : false) extends 1 ? true : false; // 正常值不是 true 就是 false 所以正常值第二次判断之后返回 false
- never | A = A
- never extends any = true
- extends 计算有 3 种情况 true false never
- A extends never 除非 A 是 never 则均为假(不考虑 any)
答案
type IsNever<T extends unknown> = [T] extends [never] ? true : false;
只要我们将其装入数组中,使 never 成为其元素就可以避免上述问题了。