TS 类型系统
接口
接口(interface)主要用于类型检查,它只是一个结构契约,定义了具有相似的名称和类型的对象结构。除此之外,接口还可以定义方法和事件。
类型别名
类型别名(type)不同于 interface 只能定义对象类型,type 声明还可以定义基础类型、联合类型或交叉类型。
两者差异:
- 定义类型的范围:接口只能定义对象类型, 而类型声明可以声明任何类型,包括基础类型、联合类型或交叉类型。
- 扩展性:接口可以 extends、implements,从而扩展多个接口或类。类型没有扩展功能,只能交叉合并。
- 合并声明:定义两个相同名称的接口会合并声明,定义两个同名的 type 会出现异常。
- 类型可以获取 typeof 返回的值作为类型。
- 类型能使用 in 关键字计算属性,生成映射类型。
总的来说,公共类型先考虑用 interface 实现,不能用 interface 实现的再用 type 实现。
联合类型
联合类型能使属性为多种类型之一(它使用 | 作为标记,如 string | number)。
const say = (name: string | undefined) => {};
1
交叉类型
交叉类型类似于 JavaScript 中的 extend,从两个对象中创建一个新对象,新对象拥有着两个对象所有的功能(T & U )。
function extend<T extends object, U extends object>(first: T, second: U): T & U {
const result = <T & U>{};
for (let id in first) {
(<T>result)[id] = first[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<U>result)[id] = second[id];
}
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
元组类型
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let tuple: [string, number];
tuple = ['aaa', 111];
1
2
2
枚举
枚举(Enum)是组织收集有关联变量的一种方式,用于取值被限定在一定范围内的场景。
enum CardSuit {
Clubs,
Diamonds,
Hearts,
Spades
}
let Card = CardSuit.Clubs;
1
2
3
4
5
6
7
2
3
4
5
6
7
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
function reverse<T>(items: T[]): T[] {
const toreturn = [];
for (let i = items.length - 1; i >= 0; i--) {
toreturn.push(items[i]);
}
return toreturn;
}
1
2
3
4
5
6
7
2
3
4
5
6
7
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法,这个时候需要对泛型进行约束, 只允许传入符合条件的变量,比如要访问某个属性,先确保变量包含该属性。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
类型断言
类型断言(Type Assertion)可以用来手动指定一个值的类型。
语法:值 as 类型 (推荐) 或者 <类型>值
用途
- 将一个联合类型断言为其中一个类型
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 将一个父类断言为更加具体的子类
interface ApiError extends Error {
code: number;
}
interface HttpError extends Error {
statusCode: number;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 将任何一个类型断言为 any
(window as any).foo = 1;
1
- 将 any 断言为一个具体的类型
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
any 和 unkonwn 的异同
共同点
any 和 unknown 都可以接收任何值。
不同点
- any 绕过类型检查,因此可以访问任意方法和属性,并且可自由转换为其他任意类型。
- unknown 标识一个元素的类型未知,不可以调用任何属性或方法,也不可以转换为其他任意类型。
如果一个变量的类型是变化的(比如来自 JS 程序的对象,随时都有可能多一个属性,甚至变成完全不同的类型),就用 any。
如果一个变量的类型是固定的,但是目前还不能确定或不想确定,就用 unknown。而且要用这个变量的时候最好先断言。