以下题目来自 github 阿宝哥的 issues上。 github.com/semlinker/a…
我的ts编译器版本是
4.8.4这只是自己对题目和答案的一些见解,会的就做,不会的就查,实在不行就看答案,根据答案理解代码步骤,如果回答和见解有不对的地方还请指出,我立即修改。谢谢 😁
哈咯!靓仔!经过前面十道题的历练,应该已经懂很多了吧,接下来的题可能会稍微偏简单一点。那就一起来刷题吧!如果前十道题还没看,那快去看一下吧! juejin.cn/post/715539…
第十一题: 实现一个 IsEqual 工具类型,用于比较两个类型是否相等
type IsEqual<A, B> = // 你的实现代码
// 测试用例
type E0 = IsEqual<1, 2>; // false
type E1 = IsEqual<{ a: 1 }, { a: 1 }> // true
type E2 = IsEqual<[1], []>; // false
解: IsEqual
// A extends B && B extends A
type IsEqual<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false
type E0 = IsEqual<1, 2>; // false
type E1 = IsEqual<{ a: 1 }, { a: 1 }> // true
type E2 = IsEqual<[1], []>; // false
答: 因为 ts操作符 中没有=,==,===,这种符号,而且,extends 并不能判断相等,只是约束(包含)而已,所以需要 A extends B && B extends A才行。
课外:不用[]包裹,测试用例也可以实现,那为什么还要包裹一下呢?
// 如果不要[] 进行包裹,可以测试一下下面这道题目
type E3 = IsEqual<never, never>; // ?
第十二题: 实现一个 Head 工具类型,用于获取数组类型的第一个类型。
type Head<T extends Array<any>> = // 你的实现代码
// 测试用例
type H0 = Head<[]> // never
type H1 = Head<[1]> // 1
type H2 = Head<[3, 2]> // 3
解: Head
type Head<T extends Array<any>> = T extends [] ? never : T[0]
// 测试用例
type H0 = Head<[]> // never
type H1 = Head<[1]> // 1
type H2 = Head<[3, 2]> // 3
答:主要是 空数组返回never,其他返回T[0]即可。
第十三题: 实现一个 Tail 工具类型,用于获取数组类型除了第一个类型外,剩余的类型。
type Tail<T extends Array<any>> = // 你的实现代码
// 测试用例
type T0 = Tail<[]> // []
type T1 = Tail<[1, 2]> // [2]
type T2 = Tail<[1, 2, 3, 4, 5]> // [2, 3, 4, 5]
解:Tail
type Tail<T extends Array<any>> = T extends [infer First,...infer Last] ? Last : []
// 测试用例
type T0 = Tail<[]> // []
type T1 = Tail<[1, 2]> // [2]
type T2 = Tail<[1, 2, 3, 4, 5]> // [2, 3, 4, 5]
答: 又用到了上次学到的infer操作符,现在已经用的很熟练了吧。这道题很简单,通过infer推断,返回剩余项,否则就是 []。
第十四题:实现一个 Unshift 工具类型,用于把指定类型 E 作为第一个元素添加到 T 数组类型中。
type Unshift<T extends any[], E> = // 你的实现代码
// 测试用例
type Arr0 = Unshift<[], 1>; // [1]
type Arr1 = Unshift<[1, 2, 3], 0>; // [0, 1, 2, 3]
解: Unshift
type Unshift<T extends any[], E> = [E, ...T]
// 测试用例
type Arr0 = Unshift<[], 1>; // [1]
type Arr1 = Unshift<[1, 2, 3], 0>; // [0, 1, 2, 3]
答: em.....
第十五题: 实现一个 Shift 工具类型,用于移除 T 数组类型中的第一个类型。
type Shift<T extends any[]> = // 你的实现代码
// 测试用例
type S0 = Shift<[1, 2, 3]> // [2, 3]
type S1 = Shift<[string,number,boolean]> // [number,boolean]
解: Shift
// 这和返回剩余项有什么区别呢。对吧
type Shift<T extends any[]> = T extends [infer First, ...infer Last] ? Last : []
// 测试用例
type S0 = Shift<[1, 2, 3]> // [2, 3]
type S1 = Shift<[string,number,boolean]> // [number,boolean]
答:这和第十三题Tail有什么区别呢?好像没什么区别。
第十六题: 实现一个 Push 工具类型,用于把指定类型 V 作为最后一个元素添加到 T 数组类型中。
type Push<T extends any[], V> = // 你的实现代码
// 测试用例
type Arr0 = Push<[], 1> // [1]
type Arr1 = Push<[1, 2, 3], 4> // [1, 2, 3, 4]
解: Push
type Push<T extends any[], V> = [...T, V ]
// 测试用例
type Arr0 = Push<[], 1> // [1]
type Arr1 = Push<[1, 2, 3], 4> // [1, 2, 3, 4]
答: 这一道题也不过多解释了,只是添加到后面而已。
第十七题: 实现一个 Includes 工具类型,用于判断指定的类型 E 是否包含在 T 数组类型中。
type Includes<T extends Array<any>, E> = // 你的实现代码
// 测试用例
type I0 = Includes<[], 1> // false
type I1 = Includes<[2, 2, 3, 1], 2> // true
type I2 = Includes<[2, 3, 3, 1], 1> // true
解: Includes
type Includes<T extends Array<any>, E> = E extends T[number] ? true : false
// 测试用例
type I0 = Includes<[], 1> // false
type I1 = Includes<[2, 2, 3, 1], 2> // true
type I2 = Includes<[2, 3, 3, 1], 1> // true
答:
T[number]可以将数组变成联合类型。E extends T[number]成功就是包含在内。
第十八题: 实现一个 UnionToIntersection 工具类型,用于把联合类型转换为交叉类型。
type UnionToIntersection<U> = // 你的实现代码
// 测试用例
type U0 = UnionToIntersection<string | number> // never
type U1 = UnionToIntersection<{ name: string } | { age: number }> // { name: string; } & { age: number; }
解: UnionToIntersection
type UnionToIntersection<U> = (U extends any ? (a: U) => void : never) extends (
a: infer P
) => void
? P
: never
// 测试用例
type U0 = UnionToIntersection<string | number> // never
type U1 = UnionToIntersection<{ name: string } | { age: number }> // { name: string; } & { age: number; }
答:
-
(U extends any (a:U) => void)将传入的联合类型变成函数参数的形式展示。相当于(a:string) => void | (a:number) => void。 -
(a:infer P) => void,推断函数的参数。 -
(a:string) => void | (a:number)=>void extends (a:infer P) => void -
进行
extends分发。 -
(a:string) => void extends (a:infer P) => void -
(a:number) => void extends (a:infer P) => void -
在逆变位置中同一类型的变量有多个候选者时,会对导出,交叉类型 也就是
string & number。 -
因为
string & number并不能被执行,所以返回 never。 -
例如 U1 就可以被执行,返回
{ name: string } & { age: number }。
课外: 协变和逆变
协变和逆变 www.typescriptlang.org/docs/handbo…
// 协变位置上 会返回联合类型
type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;
// string
type T10 = Foo<{ a: string; b: string }>;
// string | number
type T11 = Foo<{ a: string; b: number }>;
// 逆变位置上,会返回交叉类型,逆变只会出现在函数形参中。
type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
? U
: never
// string
type T20 = Bar<{ a: (x: string) => void; b: (x: string) => void }>;
// string & number
type T21 = Bar<{ a: (x: string) => void; b: (x: number) => void }>;
第十九题: 实现一个 OptionalKeys 工具类型,用来获取对象类型中声明的可选属性。
type Person = {
id: string;
name: string;
age: number;
from?: string;
speak?: string;
};
type OptionalKeys<T> = // 你的实现代码
type PersonOptionalKeys = OptionalKeys<Person> // "from" | "speak"
解: OptionalKeys
// false
// type isRequred = {a?:number} extends {a:number} ? true : false
type Person = {
id: string
name: string
age: number
from?: string
speak?: string
}
// 默认遍历 K,从T中提取K值 这也是 Pick 的实现方法
type DefaultFunc<T, K extends keyof T> = {
[P in K]: T[K]
}
// 将 键值K 变成 必选
type RequiredFunc<T, K extends keyof T> = {
[P in K]-?: T[K]
}
type OptionalKeys<T> = {
[K in keyof T]: DefaultFunc<T, K> extends RequiredFunc<T, K> ? never : K
}[keyof T]
答:
-
K in keyof T,正常遍历没问题。 -
DefaultFunc<T, K>,将 K键值对提取出来成一个新的类型。 -
RequiredFunc<T,K>,将 K键值对变成必选提取出来成一个新的类型。 -
DefaultFunc<T, K> extends RequiredFunc<T,K>,其实相等于{id: string} extends {id: string},返回的都是 never。- 一直到
{from?: string} extends {from: string},返回的是键名(K === from)。
-
最终得到的类型是:
type PersonOptionalKeys = {
id: never
name: never
age: never
from?: "from"
speak?: "speak"
}
- 最后在类型最后面添加
[keyof obj]。 在第六题的时候有说过,添加[keyof obj] 可以将键值变成联合类型,并且去除了never,这一题刚好能用到。
第二十题: 实现一个 Curry 工具类型,用来实现函数类型的柯里化处理。
type Curry<
F extends (...args: any[]) => any,
P extends any[] = Parameters<F>,
R = ReturnType<F>
> = // 你的实现代码
type F0 = Curry<() => Date>; // () => Date
type F1 = Curry<(a: number) => Date>; // (arg: number) => Date
type F2 = Curry<(a: number, b: string) => Date>; // (arg_0: number) => (b: string) => Date
解:Curry
type Curry<
F extends (...args: any[]) => any,
P extends any[] = Parameters<F>,
R = ReturnType<F>
> = P extends [infer First, ...infer Last]
? Last extends []
? (arg: First) => R
: (arg_0: First) => Curry<(...args: Last) => R>
: () => R
type F0 = Curry<() => Date> // () => Date
type F1 = Curry<(a: number) => Date> // (arg: number) => Date
type F2 = Curry<(a: number, b: string) => Date> // (arg_0: number) => (arg: string) => Date
答:
F是函数,P是全部函数的参数,R是函数的返回参数。P extends [infer First, ...infer Last],推断第一项和剩余参数,Last extends [], 成功 代表只传入一个参数,返回(arg: First) => R即可。- 失败,代表传入多个参数,返回
(arg_0: First) => Curry<(...args: Last) => R>,就相当于函数返回了一个Curry,传入剩余参数(Last)就OK啦!
以上题目来自 github 阿宝哥的 issues上。 github.com/semlinker/a…
我的ts编译器版本是
4.8.4这只是自己对题目和答案的一些见解,会的就做,不会的就查,实在不行就看答案,根据答案理解代码步骤,如果回答和见解有不对的地方还请指出,我立即修改。谢谢 😁 觉得不错点个赞,再走呗!