构建 Typescript 知识体系(二)-TS 接口浅析

这是我参与更文挑战的第八天,活动详情查看:更文挑战

接口:用来约束 对象,函数,类的结构和类型。是一种代码协作的契约

例子 1:假设从后端获取数据,然后渲染到界面上

场景一:后端有时候返回多余的字段

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);
复制代码

后端有时候返回多余的字段,但是 TS 中并不会报错,是因为 TS 中采用了 “鸭式辩型法

解释:

打比方:如果一只鸟游起来像鸭子,走起来像鸭子,叫起来像鸭子   那么就可以定义为鸭子
TS 中只要传入的参数满足必要的条件,那么就是被允许的,即使传入多余的字段也可以通过类型检查

场景二:  函数直接传入对象字面量

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 },
  ],
});
复制代码

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

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

解决方式二  类型断言

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

render({
  data: [
    { id: 1, name: "A" },
    { id: 2, name: "B" },
    // age会出现报错
    { id: 3, name: "C", age: 20 },
  ],
} as Result);
复制代码

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

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 },
  ],
});
复制代码

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

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);
复制代码

场景四:字段只读

readonly: 只能读取,不能修改

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);
复制代码

场景五: 可索引类型接口

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

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

interface StringArray {
  /*
    用任意数字去索引 StringArray 都会得到一个 string类型的结果
    */
  [index: number]: string;
}

// 使用场景一:  字符串数组
let chars: StringArray = ["a", "b"];

// 使用场景二: 以数字为键 ,字符串为值得对象
let a: StringArray = { 1: "a", 2: "数字" };
复制代码

上面例子里,我们定义了 StringArray 接口,它具有索引签名。 这个索引签名表示了当用 number 去索引 StringArray 时会得到 string 类型的返回值。

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

interface Names {
  /*
    用任意字符串去索引 Names 都会得到一个 string类型的结果
    相当于一个字符串对象
    */
  [x: string]: string;
  // 后面不可以再声明number类型的成员了,否则会报错
  // y:number
}

let myObject: Names = { name: "小明", className: "一年级一班" };
复制代码

二者是可以混合使用的

// 既可以用数字去索引 Ohters,也可以用字符串索引 Ohters
interface Ohters {
  [x: string]: string;
  [y: number]: string;
}

let myObject: Ohters = {
  1: "名称",
  a: "a",
  name: "a",
  2: "数字",
};
复制代码

数字索引签名的返回值一定要是 字符串索引签名返回值的子类型
因为 JavaScript 会进行类型转换,将  number 转换为 string。这样就能保证类型的兼容性

TypeScript 支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number 来索引时,JavaScript 会将它转换成 string 然后再去索引对象。 也就是说用 100(一个 number)去索引等同于使用”100″(一个 string)去索引,因此两者需要保持一致。

// 这样就与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;
}
复制代码

一. 用变量定义函数类型

let myAdd: (x: number, y: number) => number = function (
  x: number,
  y: number,
): number {
  return x + y;
};

// 12
myAdd0(2, 10);
复制代码

函数类型包含两部分:参数类型和返回值类型。 当写出完整函数类型的时候,这两部分都是需要的

二. 用接口定义函数类型

interface Add {
  (x: number, y: number): number;
}

let myAdd: Add = (a, b) => a + b;

// 80
myAdd(30, 50);
复制代码

TypeScript 的类型系统会推断出参数类型,因为函数直接赋值给了  Add类型变量。 函数的返回值类型是通过其返回值推断出来的(此例是  number)。 如果让这个函数返回其他类型,类型检查器会警告我们函数的返回值类型与Add接口中的定义不匹配。

三. 用类型别名定义函数类型

type Add1 = (x: number, y: number) => number;

let myAdd1: Add1 = (a, b) => a + b;

// 3
myAdd1(1, 2);
复制代码

四. 混合类型的接口

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

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();
复制代码

总结

定义函数的几种方式

方式一:function

明确的指出函数参数的类型,返回值的类型则可以通过 TS 的类型推断省略

function test(x: number, y: number) {
  return x + y;
}

test(12, 15);
复制代码

方式二:通过变量定义函数类型

let myAdd0: (x: number, y: number) => number = function (
  x: number,
  y: number,
): number {
  return x + y;
};

myAdd0(2, 10);
复制代码

方式三:通过类型别名定义函数类型

type Add1 = (x: number, y: number) => number;

let myAdd1: Add1 = (a, b) => a + b;

myAdd1(1, 2);
复制代码

方式四:通过接口定义函数类型

interface Add {
  (x: number, y: number): number;
}

let myAdd: Add = (a, b) => a + b;

myAdd(30, 50);
复制代码

后三种只是定义了函数的类型,并没有真正的实现,因此在调用时,要书写函数体
在 TS 中形参和实参必须一一对应

函数中的可选参数

function myAdd(x: number, z: number, y?: number) {
  if (y) {
    return x + y + z;
  } else return x + z;
}

myAdd(1, 2);
复制代码

可选参数必须位于必选参数之后

函数中的参数默认值

function myAdd1(x: number, z = 10, y?: number) {
  if (y) {
    return x + y + z;
  } else return x + z;
}
// 30
myAdd1(20);

function myAdd2(x: number, z = 10, a: number) {
  return x + z + a;
}
复制代码
function myAdd3(x: number, y = 0, z: number, q = 1) {
  return x + y + z + q;
}

// 报错:An argument for 'y' was not provided.
myAdd3(1);
复制代码

在必选参数前,默认参数是不可省略的,必须明确的传入 undefined 来获取默认值

function myAdd3(x: number, y = 0, z: number, q = 1) {
  return x + y + z + q;
}

// 5
console.log(myAdd3(1, undefined, 3));
复制代码

函数中的剩余参数的类型

function myAdd3(x: number, ...rest: number[]) {
  return x + rest.reduce((pre, cur) => pre + cur);
}
// 16
myAdd3(10, 1, 2, 3);
复制代码

_函数重载, _

作用:不需要为了相似功能的函数取不同的函数名称。

JavaScript 本身是个动态语言。 JavaScript 里函数根据传入不同的参数而返回不同类型的数据是很常见的。
同一个函数提供多个函数类型定义来进行函数重载。 编译器会根据这个列表去处理函数的调用

为了让编译器能够选择正确的检查类型,它与 JavaScript 里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。

function add6(...rest: number[]): number;

function add6(...rest: string[]): string;

// 在一个类型更宽泛的函数中实现重载
function add6(...rest: any): any {
  let first = rest[0];
  if (typeof first === "string") {
    return rest.join("|");
  }
  if (typeof first === "number") {
    return rest.reduce((pre: number, cur: number) => pre + cur);
  }
}
// 6
add6(1, 2, 3);
// a|b|c
console.log(add6("a", "b", "c"));
复制代码

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享