Skip to content
On this page

TypeScript 类型体操通关秘籍

掘金小册

类型安全

类型安全就是保证对某种类型只做该类型允许的操作,类型检查就是为了保证类型安全。

动态类型检查和静态类型检查

动态类型检查的类型检查是在运行时做(JavaScript),静态类型检查是在运行之前的编译期做(TypeScript)。

动态类型只适合做简单的场景,对于大项目却不太适合,因为代码中可能隐藏的隐患太多了,万一线上报一个类型不匹配的错误,那可能就是大问题,而静态类型虽然会增加写代码的成本,但却能更好的保证代码的健壮性,减少 Bug。

TypeScript 为 JavaScript 提供了一套静态类型检查系统,从动态类型语言变成了静态类型语言,可以在编译期间做类型检查,提前发现一些类型安全问题。

类型体操

TypeScript 给 JavaScript 增加了一套静态类型系统,通过 TS Compiler 编译为 JS,编译的过程叫做类型检查。它并没有改变 JavaScript 的语法,只是在 JS 的基础上添加了类型语法,所以被叫做 JavaScript 的超集。

泛型,英文是 Generic Type ,通用的类型,它可以代表任何一种类型,也叫做类型参数,声明时会把变化的类型声明成泛型,在调用的时候再确定类型。

支持编程的类型系统,对传入的类型参数(泛型)做各种逻辑运算,产生新的类型,这就是类型编程。

类型

  • never 代表不可达,比如函数抛出异常的时候,返回值就是 never
  • void 代表空,可以是 undefinednever
  • any 是任意类型,任何类型都可以赋值给它,它也可以赋值给任何类型(除了 never );
  • unknown 是未知类型,任何类型都可以赋值给它,但是它不可以赋值给别的类型。

类型运算

除了值可以变化,索引也可以做变化,用 as 运算符,叫做重映射。

ts
type MapType1<T> = {
  [K in keyof T as `${K & string}${K & string}${K & string}`]: T[K]
}

模式匹配做提取

TypeScript 类型系统的模式匹配是通过 extends 对类型参数做匹配,结果保存到通过 infer 声明的局部类型变量里,如果匹配就能从该局部变量里拿到提取出的类型。

anyunknown 的区别:anyunknown 都代表任意类型,但是 unknown 只能接受任意类型的值,而 any 除了可以接受任意类型的值,也可以赋值给任意类型(除了 never)。类型体操中进场用 unknown 接受和匹配任何类型,而很少把任何类型赋值给某个类型变量。

重新构造做变换

TypeScript 类型系统支持 3 种可以声明类型的变量:type、infer、类型参数(泛型)。

TS 只用用到的类型才会做计算

套路三:递归复用做循环

分布式条件类型

联合类型中的每个类型是相互独立的,TypeScript 对它做了特殊处理,也就是遇到了字符串类型、条件类型的时候会把每个类型单独传入做计算,最后把每个类型的计算结果合并成联合类型。条件类型左边是联合类型的时候就会触发这种处理,叫做分布式条件类型。

特殊特性

never 在条件类型中比较特殊,如果条件类型左边是类型参数,并传入的是 never,那么直接返回 never

ts
type TestNever<T> = T extends number ? 1 : 2
type Test = TestNever<never> // never

any 在条件类型之也比较特殊,如果类型参数为 any,会直接返回 trueType 和 falseType 的合并。

ts
type TestAny<T> = T extends number ? 1 : 2
type Test = TestAny<any> // 1 | 2

协变 & 逆变

TS 通过给 JS 添加静态类型系统来保证了类型安全,大多数情况下不同类型之间是不能赋值的,但是为了增加系统灵活性,设计了父子类型的概念,父子类型之间自然应该能赋值,也就是会发生型变(variant)。

型变分为逆变(contravariant)和协变(covariant)。协变很容易理解,就是子类型赋值给父类型。逆变主要是函数赋值的时候函数的参数的性质,参数的父类型可以赋值给子类型,这是因为按照子类型来声明的参数,访问父类型的属性和方法自然没问题,依然是类型安全的,但反过来就不一定了。

逆变是指被赋值函数的参数要是赋值的函数参数的子类型。

怎么确定类型之间的父子关系

通过结构,更具体的那个是子类型,这里的 StudentPerson 的所有属性,并且还多了一些属性,所以 StudentPerson 的类型,注意这里用的更具体 ,而不是更多

判断联合类型父子关系时候,'a'|'b''a'|'b'|'c' 那个更具体?'a'|'b' 更具体,所以 'a'|'b''a'|'b'|'c' 的子类型。

ts
interface Person {
  name: string
  age: number
}
interface Student {
  name: string
  age: number
  study(): void
}

type Test = 'a' | 'b' extends 'a' | 'b' | 'c' ? true : false // true

过滤 classpublic 属性

keyof 只能拿到 classpublic 索引,privateprotected 的索引会被忽略。

ts
class Dong {
  public name: string
  protected age: number
  private hobbies: string[]

  constructor() {
    this.name = 'dong'
    this.age = 20
    this.hobbies = ['sleep', 'eat']
  }
}

type PublicKeys = keyof Dong // "name"