TypeScript入坑指北
类型
Typescript 包含 Javascript 的全部类型,也就是 number、boolean、string、object、bigint、symbol、undefined、null,还有包装类 Number、Boolean、String、Object、Symbol。
除此之外,Typescript 还有 void、never、enum、unknown、any、tuple(元组) 以及自定义的 type 和 interface。
null、undefined
在 Typescript 中,null、undefined 标识一个具体的类型值,被当成其他类型的子类型(在不开启 strictNullChecks 前提下),也就是说 null 和 undefined 可以赋值给其他类型的变量
void
用来表示一个函数没有显示的返回值。这里的 test2 虽然可以用 void 但其实它显示声明了 return, 只不过什么都没返回,所以用 undefined 更好。
1 |
|
void 的特殊情况
1 |
|
这里 Fun 返回值是 void,实际 fun 返回了一个字符串,这段代码并没有报错,而且 result 的类型还是 void。这虽然看起来有点奇怪,但其实 TS 并没有强制要求 void 真的不能什么都不返回,他的意思更倾向于返回值并不会被使用,就像我们写 forEach 返回值是 void ,但我们箭头函数简写一般都会省略代码块。
1 |
|
never
never 在 Typescript 中表示什么都没有,他不可以被任何类型赋值,但可以赋值给任何类型。
另外在联合类型中,never 直接被无视掉。
枚举 enum
枚举其实可以理解成我们把一些常量封装到一个命名空间里面,通过这个命名空间来给我们提供类型提示。
另外,如果你没有给你的 enum 赋值,那么他会自动从 0 开始累加赋值。如果你给某个枚举赋值数字,那么他下面的会依次累加。
1 |
|
enum 的延迟求值
在枚举中,可以通过赋值一个函数执行来进行延迟求值,但是如果使用延迟求职,他后面的值无法自己累加,所以没显示赋值的枚举不能直接在延迟求职之后。
1 |
|
双向映射
Typescript 中,值为数字的枚举成员是双向映射的,也就是你可以通过枚举成员获取到枚举值,也可以通过枚举值来找到它的枚举成员,延迟求职的枚举也不例外。
1 |
|
常量枚举
常量枚举只能通过枚举成员访问枚举值,它的编译产物和普通枚举也有所不同。
1 |
|
常量枚举编译产物 在线试一试
1 |
|
普通枚举产物 在线试一试
1 |
|
通过产物不难看出,普通枚举双向映射的原因是进行了两次赋值,在 Item 中对 枚举值和枚举对象都进行了赋值。
元组 Tuple
元组类型其实就是元素个数和类型固定的数组类型
1 |
|
接口 interface
接口一般用来描述对象、函数、构造器、索引类型(对象、class、数组)等。
1 |
|
接口可以被继承
1 |
|
还可以被实现
1 |
|
接口索引签名
当遇到一个对象有多个属性,并且属性名不确定的情况,可以通过索引签名来处理未知的属性
1 |
|
interface 和 type 区别
- type 能描述所有数据,interface 描述对象
- interface 可以同名(视为扩展),type 不能重名
- interface 可以继承(extends)和实现(implements)
- interface 可以声明属性为可选或必填,type 不行。
类型装饰
是否可选
Typescript 中通过 ? 来声明一个属性是否为可选属性
1 |
|
是否只读
Typescript 通过 readonly 来声明一个值是只读的,当只读的属性被修改报错。
类型断言
类型断言就是在 Typescript 分析的类型不符合预期时,自己手动指定一个类型。
尖括号方法
1 |
|
通过尖括号的方法来将 any 类型的 value 断言为 string 类型,调用 string 的 length 属性
as
1 |
|
as 语法进行断言应该更加常用。
非空断言
非空断言用来声明某个值一定不是 null 或 undefined。
这里的 name 可能不存在 ,所以 Typescript 警告属性可能为 undefined,此时我们就可以使用非空断言,声明值存在。
1 |
|
非空断言在处理某些属性为空值无法继续调用时很有用。
类型运算
条件类型 extends
extends 一般用作泛型类型约束,和定义条件类型
- 泛型类型约束 泛型类型约束就是限制传入参数的类型
1
2
3type ToReadonly<T extends object> = {
readonly [Key in keyof T]: T[Key]
} - 定义条件类型 定义条件类型就是在做类型运算的时候,对类型进行推导,可以简单理解为 extends 前的类型是否为后的子类型,然后通过 Javascript 里常用的三元表达式来得到计算结果。
1
type IsAny<T> = 'a' extends ('b' & T) ? true : false
推导 infer
infer 在做类型运算的时候,起到提取元素的作用,你也可以简单理解为,用 infer 声明一个变量,然后在计算中使用这个变量。
1 |
|
联合 |
和 Javascript 中的 | 作用类似,代表满足多个类型中的一个即可。
1 |
|
交叉 &
和 Javascript 中的 & 作用类似,代表需要满足全部条件,对类型做合并。
1 |
|
另外不同类型无法合并
映射类型
映射类型用于从一个现有的类型中创建新类型的方式,就是在在现有类型基础上进行属性转换
1 |
|
in 就是对 keyof T 进行遍历
keyof 是取 T 的key,可以成为索引查询
T[Key] 是取值,成为索引访问
那么通过现有类型如何创建新类型呢
1 |
|
首先可以对索引的 value 进行修改,这里让每个值变成相同类型的长度为2的数组
1 |
|
索引当然也可以修改,通过 as 做重映射,重新定义索引名。
1 |
|
类型守卫
先看一段代码
1 |
|
这段代码我们判断入参 value 是否为 string 类型,这时在 if 内 value 已经是 string 类型。所以可以调用字符串的 toUpperCase 方法,下面我们把校验的过程抽离出来。
1 |
|
这时发现类型错误,value 还是 unknown 并没有因为校验而更改类型。
Typescript 通过 is 关键字来处理这种情况
1 |
|
isString 这种方法称之为类型守卫,想要通过类型守卫获得参数类型,需要通过 参数 + is + 预期类型来显示声明,这样才能在判断中矫正参数类型。
使用 is 也有一些注意的地方
- 必须返回一个布尔值。
- 通常结合 if、else 等条件语句使用,以根据类型进行不同的逻辑处理。
- 使用 is 编译器会智能推断变量类型,就不用再显示声明了
类型系统
结构化类型系统
TypeScript 的类型系统是一种结构化类型系统,那什么是结构化类型系统?看看 ai 怎么说:
好的,没看懂,下面用代码来体验一下
1 |
|
test 方法的参数明明是 Father 类型,但我们传入的 Son 类型却没有报错,这是因为这两个接口都有相同的 name 属性,并且 Son 在 Father 的基础上多了一个 age 而已,结构化类型系统会认为 Son 是 Father 的子类 。这也就是上图中的如果两个类型的成员结构相同(即它们拥有相同的属性和方法),那么它们被认为是兼容的。另外这种子类型可以赋值给父类型的情况叫做协变。
分布式类型
图中代码使用联合类型作为参数,Typescript 遇到这种情况会把联合类型分别传入进行类型运算,然后再合并成新的联合类型,这就是分布式类型。
1 |
|
正常情况下 'a' | 'b' extends 'a'
结果应该为 false,但当我们把他们分别当作参数传入 Test 的时候,结果却发生了变化
结果变成了 boolean
这就是因为分布式的作用所致,当我们把 'a'|'b'
传入时,等同于分别传入 'a'
和 'b'
得到结果后再联合,'a'
得到的结果是 true,'b'
得到的结果是 false,联合起来就是 boolean 类型。
关闭分布式
以上得到的结果显然是不对的,那我们如何关闭分布式,让他直接对比呢
1 |
|
这里把 T extends Y
改成 [T] extends [Y]
关闭分布式,让联合类型不再分别做类型运算,而是直接进行运算,这样就可以直接运算得到符合预期的结果了。
内置高级类型
- Parameters
- ReturnType
- ConstructorParameters
- InstanceType
- ThisParameterType
- OmitThisParameter
- Partial
- Required
- Readonly
- Pick
- Record
- Exclude
- Extract
- Omit
- Awaited
- NonNullable
- Uppercase、Lowercase、Capitalize、Uncapitaliz*