此页面需要javascript支持,请在浏览器中启用javascript

TypeScript类型体操——简单篇

类型
TypeScript
泛型
共929个字,阅读时间 5 分钟
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://icebreaker.top/articles/2021/9/26-ts-type-challenges-easy

TypeScript 类型体操 —— 简单篇

类型太重要

ts 里面类型 <泛型> 太重要,很多机制也很迷惑。所以使用 type-challenges 来帮助我辅助练习。

从简单题开始

// 4-easy-pick
type MyPick<T, K extends keyof T> = { [P in K]: T[P] }

// 7-easy-readonly
type MyReadonly<T> = {
  readonly [P in keyof T]: T[P]
}

// 11-easy-tuple-to-object
type TupleToObject<T extends readonly string[]> = {
  [key in T[number]]: key
}
// 这个 case 认识到了原来数组可以通过 T[number] 来获取里面的 value

// 14-easy-first
type First<T extends any[]> = T[0]

// 18-easy-tuple-length
type Length<T extends readonly unknown[]> = T['length']
// 这个 case 让我知道,原来在设定泛型 T  类型之后,可以使用里面的一些属性和方法

// 43-easy-exclude
type MyExclude<T, U> = T extends U ? never : T
type testCase = MyExclude<'a' | 'b' | 'c', 'c' | 'd' | 'f'>
// 这个视角,先把 U 代入 extends 右边
// 然后 T 这个联合类型 从左到右 
// 'a' => T == 'a'
// 'b' => T == 'b'
// 'c' => never
// result = 'a' | 'b' | never = 'a' | 'b'

// 268-easy-if
type If<C extends boolean, T, F> = C extends true ? T : F
// 这里要声明 condition C 为 boolean,不然 null 这些非 true 不会报错

// 3057-easy-push
type Push<T, U> = T extends [...infer R] ? [...R, U] : never
// infer R 和 [...infer R] ,左边那个不知道是啥,右边那个是个数组

// 3060-easy-unshift
type Unshift<T, U> = T extends [...infer R] ? [U, ...R] : never
// 问题和 533-easy-concat 一样,为啥不能用 R['unshift'](U) 呢?

存在的一些问题

189-easy-awaited

// 189-easy-awaited
type Awaited<T extends Promise<unknown>> = T extends Promise<infer R>
  ? R
  : never
// 非常实用,从这个case,认识了 infer 这个关键词
// 仅条件类型的 "extends" 子句中才允许 "infer" 声明。
// 为什么 infer 只能放在右边呢? 推测还是需要类型吧.

533-easy-concat

// 533-easy-concat
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]
// 一开始没想到,ts里也能用数组展开符号,问题来了
// 为什么不能否写成这样呢?笑~
type Concat<T extends unknown[], U extends unknown[]> = T['concat'](U)

898-easy-includes

// 898-easy-includes
// 最令人疑惑的题目来了
type Includes<T extends readonly any[], U> = {
  [K in T[number]]: true
}[U] extends true
  ? true
  : false
// 这个思路,一个严重的问题,这个实际上做的是,数组转对象,key为数组对应索引的值,默认附值为 ture
// 然后去判断 U 这个 key 是否为 true, true 就说明有这个类型,false则无
// 但是case报错了
Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>
// 结果为 false
// debug
type testCase = Includes<[false, 2, 3, 5, 6, 7], false> 
// false
type part1<T extends readonly any[], U> = {
  [K in T[number]]: true
}
type t = part1<[false, 2, 3, 5, 6, 7], false>
type t = {
    6: true;
    2: true;
    3: true;
    5: true;
    7: true;
}
// 为啥 false 被去掉了?

// 这个答案是issue里面找到的
type Includes<T extends readonly any[], U> = T extends [infer L, ...infer R]
  ? [U, L] extends [L, U]
    ? true
    : Includes<R, U>
  : false

// 结果

Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>
Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>

// 这 2 个  readonly 的 case 挂了
// 这个答案看不懂得地方在于
// T extends [infer L, ...infer R]
// [U, L] extends [L, U]
// 和 Includes 泛型递归
// 焦头烂额

3312-easy-parameters

// 3312-easy-parameters
type MyParameters<T extends (...args: any[]) => any> = T extends (
  ...args: infer R
) => any
  ? R
  : never
// 这里为什么 infer R ,和 [...infer R] 都可以?
type MyParameters<T extends (...args: any[]) => any> = T extends (
  ...args: [...infer R]
) => any
  ? R
  : never

结论

简单的泛型体操,就搞得焦头烂额,平时自己使用的声明,还是太简单了,2 个领悟:

  1. TypeScript 类型系统的知识,能做的非常复杂,要花力气学习
  2. 平时自己在这里投入的精力不够,简单题都做的这么吃力。

附录

深入理解 TypeScript 中文版

type-challenges