TypeScript 类型体操通关秘籍
类型安全
类型安全就是保证对某种类型只做该类型允许的操作,类型检查就是为了保证类型安全。
动态类型检查和静态类型检查
动态类型检查的类型检查是在运行时做(JavaScript),静态类型检查是在运行之前的编译期做(TypeScript)。
动态类型只适合做简单的场景,对于大项目却不太适合,因为代码中可能隐藏的隐患太多了,万一线上报一个类型不匹配的错误,那可能就是大问题,而静态类型虽然会增加写代码的成本,但却能更好的保证代码的健壮性,减少 Bug。
TypeScript 为 JavaScript 提供了一套静态类型检查系统,从动态类型语言变成了静态类型语言,可以在编译期间做类型检查,提前发现一些类型安全问题。
类型体操
TypeScript 给 JavaScript 增加了一套静态类型系统,通过 TS Compiler 编译为 JS,编译的过程叫做类型检查。它并没有改变 JavaScript 的语法,只是在 JS 的基础上添加了类型语法,所以被叫做 JavaScript 的超集。
泛型,英文是 Generic Type ,通用的类型,它可以代表任何一种类型,也叫做类型参数,声明时会把变化的类型声明成泛型,在调用的时候再确定类型。
支持编程的类型系统,对传入的类型参数(泛型)做各种逻辑运算,产生新的类型,这就是类型编程。
类型
never
代表不可达,比如函数抛出异常的时候,返回值就是never
;void
代表空,可以是undefined
或never
;any
是任意类型,任何类型都可以赋值给它,它也可以赋值给任何类型(除了never
);unknown
是未知类型,任何类型都可以赋值给它,但是它不可以赋值给别的类型。
类型运算
除了值可以变化,索引也可以做变化,用 as
运算符,叫做重映射。
type MapType1<T> = {
[K in keyof T as `${K & string}${K & string}${K & string}`]: T[K]
}
模式匹配做提取
TypeScript 类型系统的模式匹配是通过 extends
对类型参数做匹配,结果保存到通过 infer
声明的局部类型变量里,如果匹配就能从该局部变量里拿到提取出的类型。
any
和unknown
的区别:any
和unknown
都代表任意类型,但是unknown
只能接受任意类型的值,而any
除了可以接受任意类型的值,也可以赋值给任意类型(除了never
)。类型体操中进场用unknown
接受和匹配任何类型,而很少把任何类型赋值给某个类型变量。
重新构造做变换
TypeScript 类型系统支持 3 种可以声明类型的变量:type、infer、类型参数(泛型)。
TS 只用用到的类型才会做计算
分布式条件类型
联合类型中的每个类型是相互独立的,TypeScript 对它做了特殊处理,也就是遇到了字符串类型、条件类型的时候会把每个类型单独传入做计算,最后把每个类型的计算结果合并成联合类型。条件类型左边是联合类型的时候就会触发这种处理,叫做分布式条件类型。
特殊特性
never
在条件类型中比较特殊,如果条件类型左边是类型参数,并传入的是 never
,那么直接返回 never
。
type TestNever<T> = T extends number ? 1 : 2
type Test = TestNever<never> // never
any
在条件类型之也比较特殊,如果类型参数为 any
,会直接返回 trueType 和 falseType 的合并。
type TestAny<T> = T extends number ? 1 : 2
type Test = TestAny<any> // 1 | 2
协变 & 逆变
TS 通过给 JS 添加静态类型系统来保证了类型安全,大多数情况下不同类型之间是不能赋值的,但是为了增加系统灵活性,设计了父子类型的概念,父子类型之间自然应该能赋值,也就是会发生型变(variant)。
型变分为逆变(contravariant)和协变(covariant)。协变很容易理解,就是子类型赋值给父类型。逆变主要是函数赋值的时候函数的参数的性质,参数的父类型可以赋值给子类型,这是因为按照子类型来声明的参数,访问父类型的属性和方法自然没问题,依然是类型安全的,但反过来就不一定了。
逆变是指被赋值函数的参数要是赋值的函数参数的子类型。
怎么确定类型之间的父子关系
通过结构,更具体的那个是子类型,这里的 Student
有 Person
的所有属性,并且还多了一些属性,所以 Student
是 Person
的类型,注意这里用的更具体 ,而不是更多。
判断联合类型父子关系时候,'a'|'b'
和 'a'|'b'|'c'
那个更具体?'a'|'b'
更具体,所以 'a'|'b'
是 'a'|'b'|'c'
的子类型。
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
过滤 class
的 public
属性
keyof
只能拿到 class
的 public
索引,private
和 protected
的索引会被忽略。
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"