Appearance
接口 (Interfaces)
我们先定义个接口,然后把符合接口定义的对象传进去,这样能提高代码可读性。
需要注意的是:
如果我们不给传进去的对象指定是接口类型的数据,那么传入的对象参数可以包含其他属性,编译器只会检查那些必需的属性是否存在,并且其类型是否匹配。
如果我们给对象指定是接口类型的数据,那么,对象的属性必须和定义好的接口的必要属性一致。必要属性不能多也不能少。

例子 1: 假设从后端获取数据,然后渲染到界面上
场景一:后端有时候返回多余的字段
typescript
interface List {
id: number;
name: string;
}
interface Result {
data: List[];
}
function render(res: Result) {
res.data.forEach((value) => {
console.log(value.id, value.name);
});
}
let result = {
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
// 传入多余的字段如: age 却不会报错。 原理: "鸭式辩型法"
{ id: 3, name: "C", age: 20 },
],
};
render(result);
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
后端有时候返回多余的字段,但是 TS 中并不会报错,是因为 TS 中采用了 "鸭式辩型法" 解释:
打比方:如果一只鸟游起来像鸭子,走起来像鸭子,叫起来像鸭子 那么就可以定义为鸭子 TS 中只要传入的参数满足必要的条件,那么就是被允许的,即使传入多余的字段也可以通过类型检查
场景二:函数直接传入对象字面量
typescript
interface List {
id: number;
name: string;
}
interface Result {
data: List[];
}
function render(res: Result) {
res.data.forEach((value) => {
console.log(value.id, value.name);
});
}
// 注意:这里是直接给函数赋值
render({
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
// age会报错
{ id: 3, name: "C", age: 20 },
],
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
解决方式一 变量作为函数参数
跟场景一 一样。 将对象字面量赋值给一个变量, 然后,在函数中,以这个变量作为参数
解决方式二 类型断言
明确的告诉编译器 传入的参数是什么类型
typescript
render({
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
// age会出现报错
{ id: 3, name: "C", age: 20 },
],
} as Result);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
解决方式三 字符串索引签名
typescript
interface List {
id: number;
name: String;
// 用任意的字符串去索引List,可以得到任意的结果
[x: string]: any;
}
interface Result {
data: List[];
}
function render(res: Result) {
res.data.forEach((value) => {
console.log(value.id, value.name);
});
}
render({
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
{ id: 3, name: "C", age: 20 },
],
});
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
场景三:判断数据中是否存在某一个字段
typescript
interface List {
id: number;
name: String;
// 加上一个问号, 表示 age字段可以没有,也可以有
age?: number;
}
interface Result {
data: List[];
}
function render(res: Result) {
res.data.forEach((value) => {
console.log(value.id, value.name);
// 如果用到了age字段就必须进行判断
if (value.age) {
console.log(value.age);
}
});
}
let result = {
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
{ id: 3, name: "C", age: 20 },
],
};
render(result);
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
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
场景四:字段只读
readonly: 只能读取,不能修改
typescript
interface List {
// 只能读取,不能修改 否则会报错
readonly id: number;
name: String;
age?: number;
}
interface Result {
data: List[];
}
function render(res: Result) {
res.data.forEach((value) => {
console.log(value.id, value.name);
// 这里会报错
value.id = 1;
});
}
let result = {
data: [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
{ id: 3, name: "C", age: 20 },
],
};
render(result);
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
场景五:可索引类型接口
当不确定数据字段的个数的时候,就可以使用可索引的接口,可以索引接口可以是字符串类型,也可以是数字类型
用数字表示的 可索引类型接口
typescript
interface StringArray {
/*
用任意数字去索引 StringArray 都会得到一个 string类型的结果
*/
[index: number]: string;
}
// 使用场景一: 字符串数组
let chars: StringArray = ["a", "b"];
// 使用场景二: 以数字为键 ,字符串为值得对象
let a: StringArray = { 1: "a", 2: "数字" };
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
上面例子里,我们定义了 StringArray 接口,它具有索引签名。 这个索引签名表示了当用 number 去索引 StringArray 时会得到 string 类型的返回值。
用字符串表示的 可索引类型接口
typescript
interface Names {
/*
用任意字符串去索引 Names 都会得到一个 string类型的结果
相当于一个字符串对象
*/
[x: string]: string;
// 后面不可以再声明number类型的成员了,否则会报错
// y:number
}
let myObject: Names = { name: "小明", className: "一年级一班" };
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
上面例子里,我们定义了 StringArray 接口,它具有索引签名。 这个索引签名表示了当用 number 去索引 StringArray 时会得到 string 类型的返回值。 用字符串表示的 可索引类型接口
typescript
interface Names {
/*
用任意字符串去索引 Names 都会得到一个 string类型的结果
相当于一个字符串对象
*/
[x: string]: string;
// 后面不可以再声明number类型的成员了,否则会报错
// y:number
}
let myObject: Names = { name: "小明", className: "一年级一班" };
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
二者是可以混合使用的
typescript
// 既可以用数字去索引 Ohters,也可以用字符串索引 Ohters
interface Ohters {
[x: string]: string;
[y: number]: string;
}
let myObject: Ohters = {
1: "名称",
a: "a",
name: "a",
2: "数字",
};
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
数字索引签名的返回值一定要是 字符串索引签名返回值的子类型
因为 JavaScript 会进行类型转换,将 number 转换为 string。这样就能保证类型的兼容性
TypeScript 支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number 来索引时,JavaScript 会将它转换成 string 然后再去索引对象。 也就是说用 100(一个 number)去索引等同于使用"100"(一个 string)去索引,因此两者需要保持一致。
typescript
// 这样就与string不兼容了
interface Ohters1 {
[x: string]: string;
// 这里会报错
[y: number]: number;
}
// 解决方式一
interface Ohters2 {
[x: string]: any;
[y: number]: number;
}
// 解决方式二
interface Ohters3 {
[x: string]: number;
[y: number]: number;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
用变量定义函数类型
javascript
interface Add {
(x: number, y: number): number;
}
let myAdd: Add = (a, b) => a + b;
// 80
myAdd(30, 50);
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
TypeScript 的类型系统会推断出参数类型,因为函数直接赋值给了 Add 类型变量。 函数的返回值类型是通过其返回值推断出来的(此例是 number)。 如果让这个函数返回其他类型,类型检查器会警告我们函数的返回值类型与 Add 接口中的定义不匹配。
用类型别名定义函数类型
javascript
type Add1 = (x: number, y: number) => number;
let myAdd1: Add1 = (a, b) => a + b;
// 3
myAdd1(1, 2);
1
2
3
4
5
6
2
3
4
5
6
混合类型的接口
这种接口,既可以定义函数,也可以像对象一样,拥有属性和方法
javascript
interface Lib {
(): void;
version: string;
doSomeThing(): void;
}
function getLib() {
let lib: Lib = (() => {}) as Lib;
lib.version = "1.0";
lib.doSomeThing = () => {};
return lib;
}
let lib1 = getLib();
lib1();
lib1.doSomeThing();
let lib2 = getLib();
lib2();
lib2.doSomeThing();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24