接口(Interfaces)是 TypeScript 中的一个重要特性。接口是一个强大的方式来定义和保证代码符合某种特定结构。接口可以用来描述对象的结构,作为类的公共接口,或定义为函数或构造函数的类型。
一、对象类型接口
1. 对象类型接口的基本语法
下面是一个模拟处理后端返回的数据的场景案例。
// 对象类型接口
interface Some {
id: number;
name: string;
}
interface Res {
data: Some[]
}
function render(target: Res) {
target.data.forEach((value) => {
console.log(value.id + ' ' + value.name);
});
}
var information = {
data: [
{ id: 1001, name: 'zhangsan' },
{ id: 1002, name: 'lisi'}
]
}
render(information);
// "1001 zhangsan"
// "1002 lisi"
在这个案例中,我们定义了一个对象接口 Some,类型为这个接口的对象需要有 id 和 name 两个属性。又定义了一个接口 Res,表示后端返回的数据,类型为这个接口的对象需要有一个 data 属性,类型为数组,数组的元素的类型为 Some,即一个对象数组。我们还定义了一个 render 函数,用来处理后端返回的数据,参数是一个类型为 Res 的对象。
接下来我们定义了一个对象 information,遵循 Res 接口定义的规范。调用 render 函数处理这个对象,得到了我们预期的输出。
2. 对象类型接口检查机制
很多时候后端返回的数据并不是完全符合我们定义的接口规范,data 中的元素经常会有一些其他属性。修改 information 对象:
var information = {
data: [
{ id: 1001, name: 'zhangsan', message: 'hello interface' },
{ id: 1002, name: 'lisi'}
]
}
运行后我们发现仍然可以编译通过,这是 TS 接口的一个特性。
如果我们使用对象类型注解,如:
let obj: { x: number, y: number } = {x: 1, y: 2, z: 3};
就会报错:Type '{ x: number; y: number; z: number; }' is not assignable to type '{ x: number; y: number; }'. Object literal may only specify known properties, and 'z' does not exist in type '{ x: number; y: number; }'.,表示类型不匹配,多出了一个属性 z,对象的结构必须和对象类型注解一致。
但使用对象类型接口时,只要这个对象符合接口定义的规范,即有接口中定义的所有属性,且类型正确,即使有额外的属性,也可以通过类型检查。这种情况被称作 “鸭式辨型法” 或 “结构性子类型化”。
3. 对象字面量场景
上述案例中,如果我们传给 render 函数的参数不是一个对象,而是一个对象字面量,如:
render({
data: [
{ id: 1001, name: 'zhangsan', message: 'hello interface' },
{ id: 1002, name: 'lisi'}
]
});
会出现报错:Type '{ id: number; name: string; message: string; }' is not assignable to type 'Some'. Object literal may only specify known properties, and 'message' does not exist in type 'Some'.,表示对象字面量可以指定已知的属性,并且 message 属性在 Some 接口中不存在。
使用变量传值和使用字面量传值这两种写法的效果是一模一样的,为什么会出现报错呢?原因在于 TS 对对象字面量会进行一些额外的属性检查。
绕开这种检查有3种方式:
(1) 使用变量存储对象字面量,也就是上面的案例,这是最简单的方法。
(2) 使用类型断言
render({
data: [
{ id: 1001, name: 'zhangsan', message: 'hello interface' },
{ id: 1002, name: 'lisi'}
]
} as Res);
在对象字面量后加上类型断言 as Res,表示告诉编译器这是一个对象字面量,但我已经知道它会遵循 Res 接口定义的规范。
还有一种类型断言方式,使用尖括号来指定类型。如:
render(<Res>{
data: [
{ id: 1001, name: 'zhangsan', message: 'hello interface' },
{ id: 1002, name: 'lisi' }
]
});
这种方式并不推荐使用,因为某些情况下编辑器可能认为尖括号(<>)是一个标签,会出现其他问题(如在 TS 演练场就会报错)。
(3) 使用字符串的索引
告诉编辑器,我当前的 Some 接口中,除了 id 和 name 外,很可能还会有一些多余的字段存在,你要放过它们。具体方法如下:
interface Some {
id: number;
name: string;
[propName: string]: any; // 字符串索引,表示索引的类型为 string,值的类型为 any
}
render({
data: [
{ id: 1001, name: 'zhangsan', message: 'hello interface' },
{ id: 1002, name: 'lisi' }
]
});
这个时候我们也能绕开类型检查。
这里还有另一种方法,上面这个案例我并不知道多余字段的名称,也不知道多余字段有多少个,因此就使用了字符串索引。但假设我已经知道多余字段的名称,就可以按下面这样做:
interface Some {
id: number;
name: string;
message?: string;
}
render({
data: [
{ id: 1001, name: 'zhangsan', message: 'hello interface' },
{ id: 1002, name: 'lisi' }
]
});
message 属性后面的问号代表 message 这个属性可以有也可以没有,即可选属性。这样做也可以通过类型检查。
TS 还可以声明只读属性,只需要在属性名前面加上 readonly 即可。如:
interface Some {
readonly id: number;
name: string;
message?: string;
}
function render(target: Res) {
target.data.forEach((value) => {
console.log(value.id + ' ' + value.name);
value.id++;
});
}
在这个案例中,我们将 id 属性声明成只读属性,又在 forEach 中对 id 的值进行自增。会出现报错:Cannot assign to 'id' because it is a read-only property.,表示不能给 id 赋值,因为它是一个只读属性。
二、函数类型接口
首先先来复习一下,在变量上定义函数的类型的方法:
let fun: (x: number, y:number) => number;
fun = (x, y) => x + y;
1. 类型别名
type fun = (x: number, y:number) => number;
let fun: fun = (x, y) => x + y;
我们定义了一个类型别名 fun,表示一个函数类型。又定义了一个函数 fun,类型为 fun。这种写法和在变量上定义函数的类型的写法类似,但本质是不同的,前者可以一对多,后者只能一对一。
类型别名也适用于其他类型,如:
type num = number;
let fun: num = 1;
2. 函数类型接口的基本语法
使用函数类型接口,我们不需要去定义函数的名称,直接定义函数的参数和返回值的类型就可以了。
// 函数类型接口
interface Func {
(x: number, y: number): number;
}
实现方式如下:
let fn: Func = (x, y) => x + y;
使用这个函数时需要遵循 Func 接口定义的的参数和返回值类型。
3. 混合类型接口
混合类型接口可以有不同类型的成员。
// 混合类型接口
interface Func {
(): void; // 1
version: string, // 2
isObject(x: object): boolean // 3
}
接下来我们来实现这个接口:
// 混合类型接口
let fn: Func = () => {};
fn.version = '1.0.0';
fn.isObject = (x) => {
return typeof x === 'object';
};
这里我们定义了一个变量 fn,它的类型是 Func,它的值为一个没有参数没有返回值的函数,符合条件1;又给它定义一个属性 version 为一个字符串,符合条件2;再定义它的 isObject 属性为一个函数,有一个参数 x,返回类型为布尔值。
到这一步运行后仍然会出现报错:Type '() => void' is missing the following properties from type 'Func': version, isObject,表示还不符合 Func 的第2和第3个条件。
这里我们需要通过类型断言,来告诉编辑器 fn 变量满足 Func 定义的类型规范。如下所示:文章来源:https://www.toymoban.com/news/detail-452402.html
let fn: Func = (() => {}) as;
fn.version = '1.0.0';
fn.isObject = (x) => {
return typeof x === 'object';
};
这里我们才算完成对 Func 接口的实现。文章来源地址https://www.toymoban.com/news/detail-452402.html
到了这里,关于TypeScript4-接口的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!