TS的核心之一就是对数据所具有的结构进行类型检查,除了一些上篇提到的基本的类型标注,针对对象类型,还可以使用接口interface进行标注。
而且,TS除了一些基础的类型之外,还有一些高级的类型。
本篇就针对 接口interface 和 高级类型 进行讨论。
接口 interface
接口:对复杂的对象类型进行标注的一种方式 或者 给其他代码定义一种契约(比如:类)。
interface Obj {
x: number;
y: number;
};
let obj: Obj = {
x: 100,
y: 100,
};
注意:接口是一种类型,不能作为值使用。
可选属性
接口中可以定义属性是否可选,通过?进行标注。
interface Obj {
x: number;
y: number;
color?: string;
}
只读属性
只读属性readonly可以标注属性为只读,标注只读之后,除了初始化都不能给该属性赋值。
interface Obj {
readonly x: number;
readonly y: number;
}
任意属性
我们可以通过需求给接口添加任意的属性,可以通过索引类型来实现。
数字类型索引
interface Obj {
x: number;
y: number;
[prop: number]: number;
}
字符串类型索引
interface Obj {
x: number;
y: number;
color?: number;
[prop: string]: number | undefined;
}
数字类型索引是字符串类型索引的子类型
索引只能是数字或者是字符串,两者也可以同时出现。(数字类型索引中的值类型必须要是要么与字符串类型索引一致,要么是字符串类型索引的子类型)
interface Obj {
[prop1: string]: string;
[prop2: number]: string;
}
使用接口interface描述函数
比如:
interface IFunc {
(a: string, b: number): string;
}
let fn: IFunc = function(x: string, y: number): string {
return x + y;
};
function todo(callback: IFunc) {
// ....
let v = callback('1', 2);
// ....
}
todo(function(a: string, b: number): string {
return a + b;
});
interface IEventFunc {
(e: MouseEvent): void
};
function on(el: HTMLElement, evname: string, callback: IEventFUnc) {
};
let div = document.querySelector("div");
if(div) {
on(div, 'click', function(e) {});
}
接口合并
接口合并 即 多个同名的接口 合并成 一个接口。
- 如果合并的接口存在同名的非函数成员,则必须保证他们类型一致,否则编译报错;
- 接口中的同名函数则是采用重载。
interface Box {
height: number;
width: number;
fn(a: string): string;
};
interface Box {
scale: number;
fn(a: number): number;
};
let box: Box = {
height: 5,
width: 6,
scale: 10,
fn: function(a: any): any {
return a;
},
};
高级类型
联合类型
联合类型可以实现让我们选择多个类型的其中一种。(= 多选类型)
function css(ele: Element, attr: string, value: string | number) {
// ...
}
let box = document.querySelector('.box');
// document.querySelector 方法返回值就是一个联合类型
if(box) {
// ts 会提示有 null 的可能性,加上判断更严谨
css(box, 'width', '100px');
css(box, 'opacity', 1);
}
交叉类型
交叉类型可以把多种类型合并在一起拼成一种新的类型。(= 合并类型)
interface o1 {
x: number,
y: string,
};
interface o2 {
z: number,
};
let o: o1 & o2 = Object.assign({}, {x: 1, y: '2'}, {z: 100});
字面量类型
有的时候,我们希望标注的不是某个类型,而是一个固定值,就可以使用字面量类型,配合联合类型会更有用。
function setPosition(ele: Element, direction: 'left' | 'top' | 'right' | 'bottom') {}
// ok
box && setPosition(box, 'bottom');
// error
box && setPosition(box, 'here');
类型别名 type
相对于比较复杂的类型标注,我们可以用类型别名type对其进行标注,起一个简单的名字,然后再应用到其他地方。
type dir = 'left' | 'top' | 'right' | 'bottom';
function setPosition(ele: Element, direction: dir) {}
使用类型别名 type 定义函数类型
type callback = (a: string) => string;
let fn: callback = function(a) {};
// 或者直接可以表示成
let fn: (a: string) => string = function(a) {};
interface 和 tyep 的区别
interface只能描述object | class | function三种类型,且同名的interface可以合并。
而,type可以描述所有的类型数据,但是不能够重名。
类型推导
TS不需要每次都显式标注类型,可以通过类型推导根据上下文推导出相对应的类型。
// 自动推断 x 为 number
let x = 1;
// 不能将类型“"a""分配给类型"number"
x = "a";
// 函数参数类型、函数返回值会根据对应的默认值和返回值进行自动推断
function fn(a = 1) {
return a * a; // number
}
类型断言
有时候,我们标注的类型不够精确,不能使用它们的一些属性和方法,需要通过类型断言精确类型,这种类型断言,类似于一种类型转换。比如,
let img = document.querySelector("#img");
img类型被限制在Element中,不能访问src属性,我们需要标注的更为精确:HTMLImageELement类型,这个时候需要类型断言,它类似于一种类型转换:
let img = <HTMLImageElement>document.querySelector("#img");
或者
let img = document.querySelector("#img") as HTMLImageElement;
注意: 断言只是一种预判,并不会给数据本身产生实际的作用,即:类似转换,但并非真的转换了