start
TypeScript 类型系统中的类型
静态类型系统的目的是把类型检查从运行时提前到编译时,那 TS 类型系统中肯定要把 JS 的运行时类型拿过来,也就是 number、boolean、string、object、bigint、symbol、undefined、null
这些类型,还有就是它们的包装类型 Number、Boolean、String、Object、Symbol
。
这些很容易理解,给 JS 添加静态类型,总没有必要重新造一套基础类型吧,直接复用 JS 的基础类型就行。
复合类型方面,JS 有 class、Array,这些 TypeScript 类型系统也都支持,但是又多加了三种类型:元组(Tuple)
、接口(Interface)
、枚举(Enum)
元组
元组(Tuple)
就是元素个数和类型固定的数组类型
type Tuple = [number, string];
接口
接口(Interface)
可以描述函数、对象、构造器的结构
对象
interface IPerson {
name: string;
age: number;
}
class Person implements IPerson {
name: string;
age: number;
constructor(name: string, age: number) {
this.age = age;
this.name = name;
}
}
const obj: IPerson = {
name: 'decade',
age: 18,
};
函数
interface SayHello {
(name: string): string;
}
const func: SayHello = (name: string) => {
return 'hello,' + name;
};
构造器
interface ArrayConstructor<T = any> {
new (): Array<T>;
}
function createArray<T>(ctor: ArrayConstructor<T>): Array<T> {
return new ctor<T>();
}
对象类型、class
类型在 TypeScript
里也叫做索引类型,也就是索引了多个元素的类型的意思。对象可以动态添加属性,如果不知道会有什么属性,可以用可索引签名
interface IPerson {
[prop: string]: string | number;
}
const obj: IPerson = {};
obj.name = 'decade';
obj.age = 18;
枚举
枚举(Enum)
是一系列值的复合
enum Transpiler {
Babel = 'babel',
Postcss = 'postcss',
Terser = 'terser',
Prettier = 'prettier',
TypeScriptCompiler = 'tsc',
}
const transpiler = Transpiler.TypeScriptCompiler;
字面量类型
字符串的字面量类型有两种: 一种是普通的字符串字面量,比如 'aaa',另一种是模版字面量,比如 aaa${string},它的意思是以 aaa 开头,后面是任意 string 的字符串字面量类型
所以想要约束以某个字符串开头
的字符串字面量类型时可以这样写
四种特殊类型
never
代表不可达,比如函数抛异常的时候,返回值就是 nevervoid
代表空,可以是 undefined 或 neverany
是任意类型,任何类型都可以赋值给它,它也可以赋值给任何类型(除了 never)unknown
是未知类型,任何类型都可以赋值给它,但是它不可以赋值给别的类型
类型的装饰
除了描述类型的结构外,TypeScript 的类型系统还支持描述类型的属性,比如是否可选
,是否只读等
interface IPerson {
readonly name: string;
age?: number;
}
type tuple = [string, number?];
TypeScript 类型系统中的类型运算
我们知道了 TypeScript 类型系统里有哪些类型,那么可以对这些类型做什么类型运算呢?
条件类型(Conditional Type):extends ? :
下面的就是 TypeScript 类型系统里的 if else
type res = 1 extends 2 ? true : false;
上面的例子我们一眼就能看出, 这样是没必要的, 所以类型运算逻辑
都是用来做一些动态的类型
的运算的,也就是对类型参数
的运算
type isTwo<T> = T extends 2 ? true : false;
type res = isTwo<1>;
type res2 = isTwo<2>;
这种类型也叫做高级类型
高级类型的特点是传入类型参数,经过一系列类型运算逻辑后,返回新的类型
推导 infer
提取类型的一部分 如下提取 元组
的第一个元素,
提示
注意,第一个 extends
不是条件,条件类型是 extends ? :,这里的 extends
是约束的意思,也就是约束类型参数只能是数组类型。
因为不知道数组元素的具体类型,所以用 unknown
type First<Tuple extends unknown[]> = Tuple extends [infer T, ...infer R]
? T
: never;
type res = First<[1, 2, 3]>;
联合类型 |
联合类型(Union)
类似 js 里的或运算符 |
,但是作用于类型,代表类型可以是几个类型之一
type Union = 1 | 2 | 3;
交叉类型 &
交叉类型(Intersection)
类似 js 中的与运算符 &
,但是作用于类型,代表对类型做合并
type ObjType = { a: number } & { c: boolean };
提示
注意,同一类型可以合并,不同的类型没法合并,会被舍弃
type res = '111' & 222; // never
映射类型
映射类型就相当于把一个集合
映射到另一个集合
对象、class 在 TypeScript 对应的类型是索引类型(Index Type)
,那么如何对索引类型作修改呢?
type MapType<T> = {
[Key in keyof T]?: T[Key];
};
keyof T
是查询索引类型中所有的索引,叫做索引查询T[Key]
是取索引类型某个索引的值,叫做索引访问in
是用于遍历联合类型的运算符
例子: 把一个索引类型的值变成 3 个元素的数组
type MapType<T> = {
[Key in keyof T]: [T[Key], T[Key], T[Key]];
};
type res = MapType<{ a: 1; b: 2 }>;
除了值可以变化,索引也可以做变化,用 as
运算符,叫做 重映射
type MapType<T> = {
[Key in keyof T as `${Key & string}${Key & string}${Key & string}`]: [
T[Key],
T[Key],
T[Key],
];
};
&string
&string
因为索引类型(对象、class 等)可以用 string、number 和 symbol
作为 key,这里 keyof T 取出的索引就是 string | number | symbol 的联合类型,和 string 取交叉部分
就只剩下 string 了。就像前面所说,交叉类型会把同一类型做合并,不同类型舍弃