一、什么是接口

https://segmentfault.com/a/1190000010979494

接口的声明

在前面我们通过 type 可以用来声明一个对象类型

对象的另外一种声明方式就是通过接口来声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type InfoType = {
name: string;
age: number;
};

interface IInfoType {
name: string;
age: number;
}

let p: InfoType = {
name: "cc",
age: 79,
};

let p2: IInfoType = {
name: "bb",
age: 90,
};

export {};

一般以 I 开头表明是个接口。

索引类型

使用 interface 来定义对象类型,这个时候其中的属性名、类型、方法都是确定的,但是有时候我们会遇到类似下面的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 通过interface来定义索引类型
interface IndexLanguage {
[index: number]: string;
}
const frontLanguage: IndexLanguage = {
0: "HTML",
1: "CSS",
2: "JavaScript",
3: "Vue",
};

interface ILanguageYear {
[name: string]: number;
}
const languageYear: ILanguageYear = {
C: 1972,
Java: 1995,
JavaScript: 1996,
TypeScript: 2014,
};

函数类型

前面我们都是通过 interface 来定义对象中普通的属性和方法的,实际上它也可以用来定义函数类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// type CalcFn = (n1: number, n2: number) => number
// 可调用的接口
interface CalcFn {
(n1: number, n2: number): number;
}

function calc(num1: number, num2: number, calcFn: CalcFn) {
return calcFn(num1, num2);
}

const add: CalcFn = (num1, num2) => {
return num1 + num2;
};

calc(20, 30, add);

除非特别的情况,还是推荐使用类型别名来定义函数

1
type CalcFn = (n1: number, n2: number) => number;

交叉类型

我们学习了联合类型:

联合类型表示多个类型中一个即可

1
2
3
// 一种组合类型的方式: 联合类型
type WhyType = number | string;
type Direction = "left" | "right" | "center";

还有另外一种类型合并,就是交叉类型(Intersection Types)

交叉类似表示需要满足多个类型的条件

交叉类型使用 & 符号

我们来看下面的交叉类型

1
2
// 另一种组件类型的方式: 交叉类型
type WType = number & string;

表达的含义是 number 和 string 要同时满足

但是有同时满足是一个 number 又是一个 string 的值吗?其实是没有的,所以 MyType 其实是一个 never 类型

在开发中,我们进行交叉时,通常是对对象类型进行交叉的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface ISwim {
swimming: () => void;
}

interface IFly {
flying: () => void;
}

type MyType1 = ISwim | IFly;
type MyType2 = ISwim & IFly;

const obj1: MyType1 = {
flying() {},
};

const obj2: MyType2 = {
swimming() {},
flying() {},
};

接口的继承

接口和类一样是可以进行继承的,也是使用 extends 关键字

并且接口是支持多继承的(类不支持多继承)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface ISwim {
swimming: () => void;
}

interface IFly {
flying: () => void;
}

interface IAction extends ISwim, IFly {}

const action: IAction = {
swimming() {},
flying() {},
};

接口的实现

接口定义后,也是可以被类实现的

如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入

这就是面向接口开发

1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
interface ISwim {
swimming: () => void;
}

interface IEat {
eating: () => void;
}

// 类实现接口
class Animal {}

// 继承: 只能实现单继承
// 实现: 实现接口, 类可以实现多个接口
class Fish extends Animal implements ISwim, IEat {
swimming() {
console.log("Fish Swmming");
}

eating() {
console.log("Fish Eating");
}
}

class Person implements ISwim {
swimming() {
console.log("Person Swimming");
}
}

// 编写一些公共的API: 面向接口编程
function swimAction(swimable: ISwim) {
swimable.swimming();
}

// 1.所有实现了接口的类对应的对象, 都是可以传入
swimAction(new Fish());
swimAction(new Person());

swimAction({ swimming: function () {} });

interface 和 type 的区别

我们会发现 interface 和 type 都可以用来定义对象类型,那么在开发中定义对象类型时,到底选择哪一个呢

如果是定义非对象类型

  • 通常推荐使用 type,比如 Direction、Alignment、一些 Function

如果是定义对象类型,那么他们是有区别

  • interface 可以重复的对某个接口来定义属性和方法
  • 而 type 定义的是别名,别名是不能重复的
1
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
27
28
29
30
31
32
33
34
35
36
// interface定义对象类型允许别名
interface IFoo {
name: string;
}

interface IFoo {
age: number;
}

// 相当于合并
const foo: IFoo = {
name: "why",
age: 18,
};

// Window Document Date等类型是ts自己的库实现的
// 所以interface Window{} 相当于合并 - - 不建议这么做
document.getElementById("app") as HTMLDivElement;
window.addEventListener;

interface Window {
age: number;
}
window.age = 19;
console.log(window.age);

//type定义对象类型不允许别名
// type IBar = {
// name: string
// age: number
// }

// type IBar = {
// }

interface IPerson {}

字面量赋值

TypeScript 在字面量直接赋值的过程中,为了进行类型推导会进行严格的类型限制

但是之后如果我们是将一个变量标识符赋值给其他的变量时,会进行 freshness 擦除操作

1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
interface IPerson {
name: string;
age: number;
height: number;
}

const info = {
name: "why",
age: 18,
height: 1.88,
address: "广州市",
};

// freshness擦除
// 相当于把address这个类型擦擦除了
const p: IPerson = info;

console.log(info);
console.log(p);

// 应用
function printInfo(person: IPerson) {
console.log(person);
}

// 代码会报错 因为多了address类型
// printInfo({
// name: "why",
// age: 18,
// height: 1.88,
// address: "广州市"
// })

const info1 = {
name: "why",
age: 18,
height: 1.88,
address: "广州市",
};
//这样可以 因为address被擦除
printInfo(info1);

枚举类型

枚举类型是为数不多的 TypeScript 特性有的特性之一

枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型

枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型

1
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
27
28
29
30
31
32
33
// type Direction = "left" | "Right" | "Top" | "Bottom"

enum Direction {
LEFT,
RIGHT,
TOP,
BOTTOM,
}

function turnDirection(direction: Direction) {
switch (direction) {
case Direction.LEFT:
console.log("改变角色的方向向左");
break;
case Direction.RIGHT:
console.log("改变角色的方向向右");
break;
case Direction.TOP:
console.log("改变角色的方向向上");
break;
case Direction.BOTTOM:
console.log("改变角色的方向向下");
break;
default:
const foo: never = direction;
break;
}
}

turnDirection(Direction.LEFT);
turnDirection(Direction.RIGHT);
turnDirection(Direction.TOP);
turnDirection(Direction.BOTTOM);

枚举类型的值

枚举类型默认是有值的,比如上面的枚举,默认值是 0 1 2 3 ….:

当然,我们也可以给枚举其他值:

也可以给他们赋值其他的类型:

1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
// type Direction = "left" | "Right" | "Top" | "Bottom"

enum Direction {
LEFT = "LEFT",
RIGHT = "RIGHT",
TOP = "TOP",
BOTTOM = "BOTTOM",
}

let name: string = "abc";
let d: Direction = Direction.BOTTOM;

function turnDirection(direction: Direction) {
console.log(direction);
switch (direction) {
case Direction.LEFT:
console.log("改变角色的方向向左");
break;
case Direction.RIGHT:
console.log("改变角色的方向向右");
break;
case Direction.TOP:
console.log("改变角色的方向向上");
break;
case Direction.BOTTOM:
console.log("改变角色的方向向下");
break;
default:
const foo: never = direction;
break;
}
}

turnDirection(Direction.LEFT);
turnDirection(Direction.RIGHT);
turnDirection(Direction.TOP);
turnDirection(Direction.BOTTOM);

export {};