《小白第一次接触 Typescript 的艰苦学习》二

139 阅读6分钟

以下题目来自 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

答:

  1. T[number] 可以将数组变成联合类型。
  2. 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; }

答:

  1. (U extends any (a:U) => void) 将传入的联合类型变成函数参数的形式展示。相当于 (a:string) => void | (a:number) => void

  2. (a:infer P) => void ,推断函数的参数。

  3. (a:string) => void | (a:number)=>void extends (a:infer P) => void

  4. 进行 extends 分发。

  5. (a:string) => void extends (a:infer P) => void

  6. (a:number) => void extends (a:infer P) => void

  7. 在逆变位置中同一类型的变量有多个候选者时,会对导出,交叉类型 也就是 string & number

  8. 因为 string & number 并不能被执行,所以返回 never。

  9. 例如 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]

答:

  1. K in keyof T,正常遍历没问题。

  2. DefaultFunc<T, K>,将 K键值对提取出来成一个新的类型。

  3. RequiredFunc<T,K>,将 K键值对变成必选提取出来成一个新的类型。

  4. DefaultFunc<T, K> extends RequiredFunc<T,K>,其实相等于

    1. {id: string} extends {id: string},返回的都是 never。
    2. 一直到 {from?: string} extends {from: string},返回的是键名(K === from)
  5. 最终得到的类型是:

type PersonOptionalKeys = {  
  id: never
  name: never
  age: never
  from?: "from"
  speak?: "speak"
}
  1. 最后在类型最后面添加[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

答:

  1. F是函数, P是全部函数的参数, R是函数的返回参数。
  2. P extends [infer First, ...infer Last],推断第一项和剩余参数,
  3. Last extends [], 成功 代表只传入一个参数,返回 (arg: First) => R即可。
  4. 失败,代表传入多个参数,返回 (arg_0: First) => Curry<(...args: Last) => R>,就相当于函数返回了一个Curry,传入剩余参数(Last)就OK啦!

以上题目来自 github 阿宝哥的 issues上github.com/semlinker/a…

我的ts编译器版本是 4.8.4

这只是自己对题目和答案的一些见解,会的就做,不会的就查,实在不行就看答案,根据答案理解代码步骤,如果回答和见解有不对的地方还请指出,我立即修改。谢谢 😁 觉得不错点个赞,再走呗!