Ts类型体操Easy部分

130 阅读4分钟

手把手一起完成Ts类型体操中的Easy部分

我们可以先了解一下类型体操的分类

  • 对属性的修饰,包括对象属性和数组元素的可选/必选、只读/可写。我们将这一类统称为属性修饰工具类型
  • 对既有类型的裁剪、拼接、转换等,比如使用对一个对象类型裁剪得到一个新的对象类型,将联合类型结构转换到交叉类型结构。我们将这一类统称为结构工具类型
  • 对集合(即联合类型)的处理,即交集、并集、差集、补集。我们将这一类统称为集合工具类型
  • 基于 infer 的模式匹配,即对一个既有类型特定位置类型的提取,比如提取函数类型签名中的返回值类型。我们将其统称为模式匹配工具类型
  • 模板字符串专属的工具类型,比如神奇地将一个对象类型中的所有属性名转换为大驼峰的形式。这一类当然就统称为模板字符串工具类型了。

实现Pick

type MyPick<T, K> = any

Pick<Type, Keys>用于构造一个类型,它是从Type类型里面挑了一些属性Keys(Keys是字符串字面量 或者 字符串字面量的联合类型), 所以Pick返回的是一个对象。

改写第一步

type MyPick<T, K> = {
  
}

我们可以从遍历K中的属性名,然后从T中取出相应的类型

image.png

但是现在还是报错的,并且测试用例也没有通过,原因是 T 中可能不存在 K中的相应类型

举个例子, 这时候Todo里面没有 'lesson' 属性,不就报错了吗!,所以我们需要用extends约束一下 K 的类型

interface Todo {
    title: string
    description: string
    completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'| 'lesson'>

答案

type MyPick<T, K extends keyof T> = {
  [Key in K ]: T[Key]
}

实现Readonly

直接遍历T中的属性,然后加上readonly关键字就好了

type MyReadonly<T> = {
  readonly [key in keyof T] :T[key]
}

元组转换为对象

怎么从元组里面拿到所有的类型

Tuple[number] // 可以获得元组所有类型的组合

type TupleToObject<T extends readonly any[]> = {
  [key in T[number]] : key
}

第一个元素

使用infer关键字,占取第一个元素,并将剩余元素收集起来。 根据测试用例的提示,当数组为空时,返回的是never类型

type First<T extends any[]> = T extends [infer one, ... any[]] ? one : never

获取元素长度

我们可以通过length属性,直接返回元素的长度

type Length<T> = T['length']

但是我们必须要对 T 进行进一步的约束,确保 T 中有length属性

type Length<T extends any[]> = T['length']

但是这时候我们还是报错,说readonly和any[]不匹配,我们要在前面加上readonly即可

image.png

type Length<T extends  readonly any[]> = T['length']

Exclude

前置知识,分发条件类型(Distributive Conditional Types)

当在泛型中使用条件类型时,如果传入的是一个联合类型,就会变成分发的 。相当于会自动一个个遍历进行判断

type StrArrOrNumArr = ToArray<string | number>;        
// type StrArrOrNumArr = string[] | number[]

了解了这些,我们就可以来实现Exclude了

因为T是联合类型,会自动遍历里面的属性, 当 T 中的属性存在于 U 时,就返回never ,否则返回遍历到的当前类型

type MyExclude<T, U> = T extends U ? never: T

Awaited

学习TypeScript28(infer 递归)_小满zs的博客-CSDN博客)

type MyAwaited<T> = T extends PromiseLike<infer K> ? MyAwaited<K> : T;

If

非常简单,不多解释

type If<C, T, F> = C  extends true ? T :F

Concat

直接用收集展开处理

type Concat<T, U> = [...T ,...U]

但是这时候却报错了,因为ts并不知道T,U是一个元组

image.png

用extends进行类型约束

type Concat<T extends any[], U extends any[]> = [...T ,...U]

Includes

思路很简单,依次遍历T里面有没有U这个属性,有的话就是true,否则返回false

来看代码怎么实现,先对比第一个,再递归对比剩余的

type Includes<T extends readonly any[], U> = T extends [infer one ,... infer rest]? 
one extends U ? true: Includes<rest,U>:
false

但是我们发现测试用例并没有全部通过

image.png 我们在判断相等的时候,使用的extends。 但是false是 boolean的子集 ,返回的是true。但是我们想进行严格判断返回false,我们可以使用它提供的Equal函数(偷懒.png)

type Includes<T extends readonly any[], U> = T extends [infer one ,... infer rest]? 
Equal<one ,U> extends true ? true: Includes<rest,U>:
false 

Push

同理

type Push<T extends any[], U> = [...T ,U]

Unshift

type Unshift<T  extends any[], U> = [U, ...T]

Parameters

把函数的参数收集起来,然后直接返回就好了

type MyParameters<T extends (...args: any[]) => any> = T extends (...rest:infer P) => any ? P : undefined