TypeScript 接口
官方介绍
TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
通俗点说,在面向对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
# 接口的定义
和 java 语言相同,TypeScript 中定义接口也是使用 interface 关键字来定义:
interface IQuery {
page: number;
}
2
3
# 接口中定义方法
看上面的接口中,我们定义了 page 常规属性,定义接口时候不仅仅可以有 属性,也可以有方法,看下面的例子:
interface IQuery {
page: number;
findOne(): void;
findAll(): void;
}
2
3
4
5
如果我们有一个对象是该接口类型,那么必须包含对应的属性和方法(无可选属性情况):
const q: IQuery = {
page: 1,
findOne() {
console.log("findOne");
},
findAll() {
console.log("findAll");
},
};
2
3
4
5
6
7
8
9
# 接口中定义属性
# 普通属性
上面的 page 就是普通属性,如果有一个对象是该接口类型,那么必须包含对应的普通属性。就不具体说了。
# 可选属性
默认情况下一个变量(对象)是对应的接口类型,那么这个变量(对象)必须实现接口中所有的属性和方法。
但是,开发中为了让接口更加的灵活,某些属性我们可能希望设计成可选的(想实现可以实现,不想实现也没有关系),这个时候就可以使用可选属性:
interface IQuery {
page: number;
findOne(): void;
findAll(): void;
isOnline?: string | number; // 是否出售中的商品
delete?(): void
}
2
3
4
5
6
7
上面的代码中,我们增加了isOnline属性和delete方法,这两个都是可选的:
注意
注意:可选属性如果没有赋值,那么获取到的值是undefined;
对于可选方法,必须先进行判断,再调用,否则会报错;
const q: IQuery = {
page: 1,
findOne() {
console.log("findOne");
},
findAll() {
console.log("findAll");
},
};
console.log(p.isOnline); // undefined
p.delete(); // 不能调用可能是“未定义”的对象。
2
3
4
5
6
7
8
9
10
11
12
正确的调用方式如下:
if (p.delete) {
p.delete();
}
2
3
大家可能会问既然是可选属性,可有可无的,那么为什么还要定义呢?对比起完全不定义,定义可选属性主要是:为了让接口更加的灵活,某些属性我们可能希望设计成可选,并且如果存在属性,能约束类型,而这也是十分关键的。
# 只读属性
默认情况下,接口中定义的属性可读可写: 但是有一个关键字 readonly,定义的属性值,不可以进行修改,强制修改后报错。
interface IQuery {
readonly page: number;
findOne(): void;
}
2
3
4
给page属性加了readonly关键字,再给它赋值会报错。
const q: IQuery = {
page: 1,
findOne() {
console.log("findOne");
},
};
q.page = 10;// Cannot assign to 'page' because it is a read-only property.
2
3
4
5
6
7
# 接口的高级篇
# 函数类型接口
Interface 还可以用来规范函数的形状。Interface 里面需要列出参数列表返回值类型的函数定义。写法如下:
定义了一个函数接口 接口接收三个参数并且不返回任何值 使用函数表达式来定义这种形状的函数
interface Func {
// ✔️ 定于这个函数接收两个必选参数都是 number 类型,以及一个可选的字符串参数 desc,这个函数不返回任何值
(x: number, y: number, desc?: string): void
}
const sum: Func = function (x, y, desc = '') {
// const sum: Func = function (x: number, y: number, desc: string): void {
// ts类型系统默认推论可以不必书写上述类型定义
console.log(desc, x + y)
}
sum(32, 22)
2
3
4
5
6
7
8
9
10
11
12
13
注意:不过上面的接口中只有一个函数,TypeScript 会给我们一个建议,可以使用 type 来定义一个函数的类型:
type Func = (x: number, y: number, desc?: string) => void;
# 接口的实现
接口除了定义某种类型规范,也可以和其他编程语言一样,让一个类去实现某个接口,那么这个类就必须明确去拥有这个接口中的属性和实现其方法: 下面的代码中会有关于修饰符的警告,暂时忽略,后面详细讲解
// 定义一个实体接口
interface Entity {
title: string;
log(): void;
}
// 实现这样一个接口
class Post implements Entity {
title: string;
constructor(title: string) {
this.title = title;
}
log(): void {
console.log(this.title);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
有些小伙伴的疑问?我定义了一个接口,但是我在继承这个接口的类中还要写接口的实现方法,那我不如直接就在这个类中写实现方法岂不是更便捷,还省去了定义接口?这是一个初学者经常会有疑惑的地方。
解答这个疑惑之前,先记住两个字,规范!
这个规范可以达到你一看这名字,就知道他是用来干什么的,并且可拓展,可以维护。
在代码设计中,接口是一种规范;
接口通常用于来定义某种规范, 类似于你必须遵守的协议,
站在程序角度上说接口只规定了类里必须提供的属性和方法,从而分离了规范和实现,增强了系统的可拓展性和可维护性;
# 接口的继承
和类一样,接口也能继承其他的接口。这相当于复制接口的所有成员。接口也是用关键字 extends 来继承。
interface Shape { //定义接口Shape
color: string;
}
interface Square extends Shape { //继承接口Shape
sideLength: number;
}
2
3
4
5
6
7
一个 interface 可以同时继承多个 interface ,实现多个接口成员的合并。用逗号隔开要继承的接口。
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
2
3
4
5
6
7
8
9
10
11
需要注意的是,尽管支持继承多个接口,但是如果继承的接口中,定义的同名属性的类型不同的话,是不能编译通过的。如下代码:
interface Shape {
color: string;
test: number;
}
interface PenStroke extends Shape{
penWidth: number;
test: string;
}
2
3
4
5
6
7
8
9
另外关于继承还有一点,如果现在有一个类实现了 Square 接口,那么不仅仅需要实现 Square 的方法,也需要实现 Square 继承自的接口中的方法,实现接口使用 implements 关键字 。
# 可索引类型接口
# interface和type的区别
type 可以而 interface 不行
type 可以声明基本类型别名,联合类型,元组等类型
// 基本类型别名
type Name = string
// 联合类型
interface Dog {
wong();
}
interface Cat {
miao();
}
type Pet = Dog | Cat
// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type 语句中还可以使用 typeof 获取实例的 类型进行赋值
// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div
2
3
4
type 其他骚操作
type StringOrNumber = string | number;
type Text = string | { text: string };
type NameLookup = Dictionary<string, Person>;
type Callback<T> = (data: T) => void;
type Pair<T> = [T, T];
type Coordinates = Pair<number>;
type Tree<T> = T | { left: Tree<T>, right: Tree<T> };
2
3
4
5
6
7
# interface 可以而 type 不行
interface 能够声明合并
interface User {
name: string
age: number
}
interface User {
sex: string
}
/*
User 接口为 {
name: string
age: number
sex: string
}
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
另外关于type的更多内容,可以查看文档:TypeScript官方文档
# 接口的应用场景总结
在项目中究竟怎么用,开篇已经举了两个例子,在这里再简单写一点,最近尝试了一下egg+ts,学习下。在写查询参数检验的时候,或者返回固定数据的时候,都会用到接口,看一段简单代码,已经看完了上面的文章,自己体会下吧。
import User from '../model/user';
import Good from '../model/good';
// 定义基本查询类型
// -- 查询列表时候使用的接口
interface Query {
page: number;
rows: number;
disabledPage?: boolean; // 是否禁用分页,true将会忽略`page`和`rows`参数
}
// 定义基本返回类型
type GoodResult<Entity> = {
list: Entity[];
total: number;
[propName: string]: any;
};
// - 商品
export interface GoodsQuery extends Query {
isOnline?: string | number; // 是否出售中的商品
goodsNo?: string; // 商品编号
goodsName?: string; // 商品名称
}
export type GoodResult = QueryResult<Good>;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26