Skip to content
On this page

接口 (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

后端有时候返回多余的字段,但是 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

解决方式一 变量作为函数参数

跟场景一 一样。 将对象字面量赋值给一个变量, 然后,在函数中,以这个变量作为参数

解决方式二 类型断言

明确的告诉编译器 传入的参数是什么类型

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

解决方式三 字符串索引签名

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

场景三:判断数据中是否存在某一个字段

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

场景四:字段只读

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

场景五:可索引类型接口

当不确定数据字段的个数的时候,就可以使用可索引的接口,可以索引接口可以是字符串类型,也可以是数字类型

用数字表示的 可索引类型接口

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

上面例子里,我们定义了 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

上面例子里,我们定义了 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

二者是可以混合使用的

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

数字索引签名的返回值一定要是 字符串索引签名返回值的子类型 因为 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

用变量定义函数类型

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

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

混合类型的接口

这种接口,既可以定义函数,也可以像对象一样,拥有属性和方法

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
沪ICP备20006251号-1