ts 的核心原则之一是对值所具体的结构进行类型检查。接口的作用就是为这些类型命名和代码定义规范。
接口初探
function printLabel(labelObj: { label: string }) {
console.log(labelObj.label);
}
printLabel({
label: "q",
});
- printLabel 函数接收一个参数
- 这个参数是一个对象
- 并要求这个对象中有一个 label 属性,而且类型为字符串
使用接口重写上面的例子
关键字 interface
// 使用接口重写
interface LabelValue {
label: string;
}
function printLabel(labelObj: LabelValue) {
console.log(labelObj.label);
}
printLabel1({
label: "q",
});
那么这么定义接口的好处是什么呢?
那其中之一就是:
比如我们又定义了一个函数,参数与 printLabel 的类型一致。我们不需要为每一个函数都定义重复的约束,直接可以使用同一个接口来约束即可
可选属性
定义可选属性,只需在属性后面加一个 ? 符号
interface LabelValue {
label: string;
value?: string;
}
function printLabel({ label, value }: LabelValue) {
let str = label;
if (value) {
str += value;
}
console.log(str);
}
printLabel({
label: "q",
});
printLabel({
label: "q",
value: "q",
});
只读属性
在属性前使用 readonly 来指定只读属性,然后尝试修改它时会报错。
interface Point {
readonly x: number;
readonly y: number;
}
let p: Point = { x: 10, y: 20 };
p.x = 20; // error
对象的属性可以定义只读属性,那么数组同样可以定义只读。
ts 具有 ReadonlyArray 类型,它与 Array 类似,只是把所有的可变方法去掉了,因此保证数组创建之后不能被修改
let arr: ReadonlyArray<string> = ["1", "2"];
arr[0] = "1"; // error
arr.push("3"); // error
我们尝试将只读数组赋值给普通的数组:
let arr: ReadonlyArray<number> = [1, 2];
let list: Array<number>;
list = arr; // 类型 "readonly number[]" 为 "readonly",不能分配给可变类型 "number[]"
上面的最后一行代码显示,把只读数组 arr 赋值到一个新数组也不行。
此时如果需要赋值,可以使用类型断言处理。
list = arr as Array<number>;
额外的属性检查
对象字面量会被特殊对待并且会经过额外的属性检查,当将他们赋值给变量或者传递给参数时。如果对象字面量存在接口不包含的属性时,就会出现错误。
说白了其实就是:
如果你没有在接口里声明这个属性,但是传递参数时,你又传了这个属性,我就说你错了!
请看下面的案例
interface LabelValue {
label: string;
value?: string;
}
function printLabel({ label, value }: LabelValue) {
console.log(label + value);
}
printLabel({
label: "q"
name: "qzy",
});
- 在接口里只定义了 label 和 value
- 但你现在传了一个 name 是什么意思?
- 我就给你提示错误,不给你通过编译
那么该如何处理呢?
最简单的方法是使用类型断言
printLabel({
label: "q",
name: "qzy",
} as LabelValue);
最佳的方案是添加一个字符串索引签名,就是预留一个属性位置
interface LabelValue {
label: string;
value?: string;
[propName: string]: any;
}
printLabel({
label: "q",
name: "qzy",
age: 18,
});
还有一种跳过类型检查的方法是:将这个对象赋值给另外一个变量
这个还是要归于额外的属性检查的定义。
let obj = {
label: "q",
name: "qzy",
age: 18,
};
printLabel(obj);
函数类型
接口不仅可以描述对象,也可以描述函数类型
这个可以这么理解:我可以约束函数的参数,返回值。只要按照规则定义好即可。
比如定义下面的函数类型:
- 参数 age 为数字
- 返回值是一个 boolean
interface IsAgent {
(age: number): boolean;
}
let fun: IsAgent = (age: number): boolean => {
return age > 18;
};
fun(19);
对于函数类型的类型检查,函数的参数名不需要与接口里定义的相匹配。
如果你不想指定类型, ts 也会进行推断,我们可以使用下面的代码进行重写
interface IsAgent {
(age: number): boolean;
}
let fun: IsAgent = (ag) => {
return ag > 18;
};
fun(12);
可以少写一些代码。
可索引的类型
描述了对象索引的类型,还有相应的索引返回值类型
可以约束对象或者数组对应的索引的类型值
// 下标为数字 值为字符串
interface StringArray {
[index: number]: string;
}
// 数组每项必须全部时字符串
let arr: StringArray = ["1", "2"];
// 下标为字符串 值为字符串
interface NumberObj {
[index: string]: number;
}
// 对象的值必须是数字
let obj: NumberObj = {
age: 18,
};
类类型
ts 也能够用接口明确的强制一个类去符合某种规则
你可以在接口里去描述一个方法,在类里去实现它
interface myPer {
name: string;
love(): void;
}
// 必须包含 name、love
class Person implements myPer {
name: string;
constructor(name: string) {
this.name = name;
}
love(): void {
console.log(`${this.name}喜欢吃肉`);
}
}
接口继承
和类一样,接口也可以相互继承。
interface Shape {
color: string;
}
interface Square extends Shape {
width: number;
}
// 必须存在 color、width 属性
let data: Square = {
color: "red",
width: 100,
};
一个接口可以继承多个接口
interface Shape {
color: string;
}
interface Square {
width: number;
}
interface Local extends Shape, Square {
local: string;
}
let data: Local = {
color: "red",
width: 100,
local: "中国",
};