TypeScript语法-- 3. 接口interface的定义,实现,继承

TypeScript系列文章

可浏览博客主页的TypeScript专栏,会陆续添加相关文章,有问题或者可以优化的地方也希望大大门告知
共同进步 :)

在这里插入图片描述



interface-接口

ts版本 Version 4.8.4

TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码进行类型限制。
接口可以对普通对象,函数,类进行类型限制!!!


接口实战

1. 常规类型限制 vs 接口类型限制 (普通对象)

存在即真理,对比才知知道优劣

这边只是用了简单的类型进行限制,好像常规类型代码量看起来比较少~

// 常规类型限制
function f1(obj: { label: string }) {
  console.log("f => obj.label:", obj.label);
}
f1({ label: "string" });
// 接口类型限制
interface objType {
  label: string;
}
function f2(obj: objType) {
  console.log("f2 => obj.label:", obj.label);
}
f2({ label: "string" });

思考:如果类型比较复杂,并且其他函数也是有相同的类型约束,需要复用...孰优孰劣呢~

// 常规类型限制
function consoleConfig(obj: { label1: string,label2:string,label3:string,label4:string}) {
  console.log("f => obj:", obj);
}
function setConfig(obj: { label1: string,label2:string,label3:string,label4:string}) {
  Window.config = obj;
}

这边的参数还是有点少,怕代码太多了…自行脑补 ;)
这样是不是就舒服多了

// 定义通用接口
interface Config{
    label1: string;
    label2: string;
    label3: string;
    label4: string;
}
// 使用接口
function consoleConfig2(obj: Config) {
  console.log("f => obj:", obj);
}
function setConfig2(obj: Config) {
  // do something...
}

2. 可选属性

好处:可以对可能存在的属性进行预定义

interface objType3 {
  label: string;
  // 可选属性
  params?: string;
}
function f3(obj: objType3) {
  console.log("f3 => obj.label:", obj.label);
  if (obj.params) {
    console.log("f3 => 可选参数,存在才触发 obj.params:", obj.params);
  }
}

// 可以打印label
f3({ label: "string" });
// 可以打印label,params是可选属性,有的话也会执行打印
f3({ label: "string", params: "test" });

3. 只读属性

const 用于变量
readonly 用于对象的属性

只读的类型限制是不能够修改的,除非你用了断言,告诉编译器,我知道这个东西,我知道怎么用,断言如下:

interface Point {
  readonly x: number;
  readonly y: number;
}
function f4(point: Point) {
  // point.x=50; // 报错,只读对象不能修改
  console.log(point);
  console.log("f4 => point:", point);
}
f4({ x: 50, y: 50 });

// 使用断言对只读属性进行修改
let a: number[] = [1, 2, 3, 4];
// ReadonlyArray<T>类型
let ro: ReadonlyArray<number> = a;
// ro[0] = 1; // Error
// 使用类型断言
a = ro as number[];

4. 额外的属性

额外的属性尽量少用,这个可能会让TS检测不出BUG,除非是调用者的一些自定义配置

interface config {
  a?: string;
  b?: string;
  [propName: string]: any; // 跳过检测
}

function f5(config: config) {
  return config;
}
// 对象的属性c 是跳过检测的...
let config = f5({ a: "aaa", c: "ccc" });

5. 可索引的类型

TypeScript支持两种索引签名:字符串和数字。

// 数字索引  针对数组
interface numberArray {
  [index: number]: string;
}
let nArr: numberArray = ["1", "2", "3"];
nArr = {
  0: "1",
  1: "2",
  2: "3",
};
// 字符串索引  针对对象
// 因为字符串索引声明了 obj.property和obj["property"]两种形式都可以。
interface StringArray {
  readonly [index: string]: string;
}
let sArr: StringArray;
// sArr[0]="0"; // Error 设置了readonly 后,只能对对象进行赋值,不能通过下标赋值~
// sArr= ["1", "2", "3"];  // Error 数组默认是字符串下标,数组的是数值
sArr = {
  "0": "1",
  "1": "2",
  "2": "3",
};

interface Shape {
  color: string;
}

6. 函数类型 (函数)

接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。

// 该接口实现了函数类型的限制
interface tsFunc {
  (str1: string, str2: string): string;
}

let myFunc: tsFunc = function (string1: string, string2: string) {
  return string1 + string2;
};

console.log(myFunc("a", "b"));
// console.log(myFunc("a",1)); // Error 函数接口已经做了限制

7. 类类型-实现implements (类)

类只要声明了成员就一定要对其进行初始化
1.初始化表达式
2.构造函数中明确赋值。
这边不对类进行多余的讲解,类将专门拿出一章节来 ;)

  1. 严格意义上构造函数不属于成员,只是为了在实例化的时候初始化一些成员,不必剑走偏锋,想要将构造函数constructor 进行限制~~~
  2. 类实现接口,所限制的只是类实现了接口的类型限制,就是类可以有自己的一些自定义的成员

如下方类Clock,接口是ClockInterface

  1. currentTime变量成员:接口有了,则类要有声明,赋值可以用表达式
  2. setTime函数成员:接口有了,则类要有声明,赋值可以在构造函数
  3. getDay函数成员:接口中是可选属性,则类中可有可无
  4. getDate函数函数:接口中没有,则类中可有可无
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
  // 这边也可以设置可选属性~
  getDay?(): number;
  // 这里注释了也没问题,因为类实现接口只需满足 接口存在的类型限制
  // 接口描述了类的部分类型限制
  // getDate(): number;
}

class Clock implements ClockInterface {
  // 当前时间
  // 初始化表达式赋值...
  currentTime = new Date();
  // 设置时间
  setTime;
  // 这边是可选属性,注释了也没问题~
  // getDay = () => {
  //   return this.currentTime.getDay();
  // };
  // 类中可以自定义成员,不需要被类类型的接口限制死
  // 获取当前几号
  getDate = () => {
    return this.currentTime.getDate();
  };
  constructor(h: number, m: number) {
    // 在构造函数中赋值...
    this.setTime = (d: Date) => {
      this.currentTime = d;
    };
  }
}

继承

继承,主要看你想要继承到什么~

  1. 继承接口
  2. 继承类的成员以及实现 (不在本章节~)
  3. 继承类的所有成员类型限制

1. 接口继承接口

接口的继承也是用关键字extends

// 定义一个 "形状" 接口,附加颜色通用属性
interface Shape {
  color: string;
}
// 定义一个 "正方形" 接口,他继承了 "形状" 接口,并且带了边长属性
interface Square extends Shape {
  sideLength: number;
}

// 调用方式1. 断言1
// TypeScript 断言 <Type>
// 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。
// 类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;

// 调用方式1. 断言2  as 语法
let square1 = {} as Square;
square1.color = "red";
square1.sideLength = 12;

// 在赋值前不能使用了变量“square2”。
let square2: Square;
// square2.color = "green";  // Error  在赋值前使用了变量“square1”
// square2.sideLength = 100;  // Error  在赋值前使用了变量“square1”

2. 接口继承类

接口继承类也是用关键字extends
场景:已经有现成的父类Father,父类的接口实现是FatherInterface

// 先来初始化父类
// 父类接口 FatherInterface
interface FatherInterface {
  // 只对name成员进行了类型限制
  name: string;
  // 只对getName成员进行了类型限制
  getName(): string;
}
// 父类 已经实现接口
class Father implements FatherInterface {
  name;
  getName = () => {
    return "Father:" + this.name;
  };
  // 这边添加了一个成员,跟FatherInterface区分开
  // 等下看看继承Father的接口是否也继承了sex的类型限制
  sex;
  constructor(name: string, sex: string) {
    this.name = name;
    this.sex = sex;
  }
}
1目标方法
2想要继承FatherInterface用接口继承接口即可
3想要继承Father中的所有成员用类继承类
4只要Fahter中的所有成员类型限制,所有的成员自己实现接口继承类
// 针对:只要Father中的所有成员类型限制,所有的成员自己赋值
// 继承父类所有的类型,不止FatherInterface接口中的name,还有Father类中sex
// 这边只是对接口进行了继承,不是对类进行继承,所以不会得到Father成员实现,只有声明~
// 所以需要重新实现
// 子类接口 Father1Interface 对age进行了类型限制
interface Father1Interface extends Father {
  age: number;
}
class Father1 implements Father1Interface  {
  //  只是继承了类的类型限制,这边需要重新声明
  name;
  //  只是继承了类的类型限制,这边需要重新声明
  sex;
  //  只是继承了类的类型限制,需要对其进行声明并且实现
  getName = () => {
    return "Father1:" + this.name;
  };
  //  普通接口成员声明
  age;
  constructor(name: string, sex: string, age: number) {
    // 只是继承了类的类型限制,这边需要重新实现
    this.name = name;
    // 只是继承了类的类型限制,这边需要重新实现
    this.sex = sex;
    // 普通接口成员实现
    this.age = age;
  }
}

// new Father1 实例化一个对象,father1
let father1 = new Father1("另外的爸爸:", "男", 68);
console.log(
  father1.getName()
);

这边可以弄个类继承类对比下

class Children extends Father {
  age: number;
  constructor(name: string, sex: string, age: number) {
    super(name, sex);
    this.age = age;
  }
}
let children = new Children("儿子", "男", 18);
console.log(
  // 子类没有重新实现,则可以调用父类的getName()
  children.getName()
);

源码

/*
 * @Author: Penk
 * @LastEditors: Penk
 * @LastEditTime: 2022-11-17 16:13:08
 * @FilePath: \typescript-tutorial\03_interface-接口\index.ts
 * @Desc: 接口interface主要用于对象变量,方便复用,以及对类进行实现接口,以便实现成员类型限制
 * @email: 492934056@qq.com
 */

// ----------------------常规类型限制---------------------- //
console.log("----------------------常规类型限制----------------------");

function f1(obj: { label: string }) {
  console.log("f => obj.label:", obj.label);
}
f1({ label: "string" });

// ----------------------接口类型限制---------------------- //
console.log("----------------------接口类型限制----------------------");
interface objType {
  label: string;
}
function f2(obj: objType) {
  console.log("f2 => obj.label:", obj.label);
}
f2({ label: "string" });

// ----------------------常规类型限制 vs 接口类型限制---------------------- //
console.log(
  "----------------------常规类型限制 vs 接口类型限制----------------------"
);
// 方法 consoleConfig  setConfig 参数都一样...
function consoleConfig(obj: {
  label1: string;
  label2: string;
  label3: string;
  label4: string;
}) {
  console.log("f => obj:", obj);
}
function setConfig(obj: {
  label1: string;
  label2: string;
  label3: string;
  label4: string;
}) {
  // do something...
}

// 定义通用接口,是不是看起来舒服一点了~~~
interface Config {
  label1: string;
  label2: string;
  label3: string;
  label4: string;
}
// 使用接口
function consoleConfig2(obj: Config) {
  console.log("f => obj:", obj);
}
function setConfig2(obj: Config) {
  // do something...
}
// ----------------------接口类型-可选属性---------------------- //
// 好处:可以对可能存在的属性进行预定义
console.log("----------------------接口类型-可选属性----------------------");
interface objType3 {
  label: string;
  // 可选属性
  params?: string;
}
function f3(obj: objType3) {
  console.log("f3 => obj.label:", obj.label);
  if (obj.params) {
    console.log("f3 => 可选参数,存在才触发 obj.params:", obj.params);
  }
}

// 可以打印label
f3({ label: "string" });
// 可以打印label,params是可选属性,有的话也会执行打印
f3({ label: "string", params: "test" });

// ----------------------接口类型-只读属性---------------------- //
// const 用于变量
// readonly 用于对象的属性
console.log("----------------------接口类型-只读属性----------------------");
interface Point {
  readonly x: number;
  readonly y: number;
}
function f4(point: Point) {
  // point.x=50; // 报错,只读对象不能修改
  console.log(point);
  console.log("f4 => point:", point);
}
f4({ x: 50, y: 50 });

// 使用断言对只读属性进行修改
let a: number[] = [1, 2, 3, 4];
// ReadonlyArray<T>类型
let ro: ReadonlyArray<number> = a;
// ro[0] = 1; // Error
// 使用类型断言
a = ro as number[];

// ----------------------接口类型-额外的属性---------------------- //
// 额外的属性尽量少用,这个可能会让TS检测不出BUG,除非是调用者的一些自定义配置
console.log("----------------------接口类型-额外的属性----------------------");
interface config {
  a?: string;
  b?: string;
  [propName: string]: any; // 跳过检测
}

function f5(config: config) {
  return config;
}

// 对象的属性c 是跳过检测的...
let config = f5({ a: "aaa", c: "ccc" });

// ----------------------接口类型-可索引的类型---------------------- //
// TypeScript支持两种索引签名:字符串和数字。
console.log("----------------------接口类型-可索引的类型--------------------");

// 数字索引  针对数组
interface numberArray {
  [index: number]: string;
}
let nArr: numberArray = ["1", "2", "3"];
nArr = {
  0: "1",
  1: "2",
  2: "3",
};

// 字符串索引  针对对象
// 因为字符串索引声明了 obj.property和obj["property"]两种形式都可以。
interface StringArray {
  readonly [index: string]: string;
}
let sArr: StringArray;
// sArr[0]="0"; // Error 设置了readonly 后,只能对对象进行赋值,不能通过下标赋值~
// sArr= ["1", "2", "3"];  // Error 数组默认是字符串下标,数组的是数值
sArr = {
  "0": "1",
  "1": "2",
  "2": "3",
};

// ----------------------接口类型-函数类型---------------------- //
// 接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
console.log("----------------------接口类型-函数类型----------------------");

interface tsFunc {
  (str1: string, str2: string): string;
}

let myFunc: tsFunc = function (string1: string, string2: string) {
  return string1 + string2;
};

console.log(myFunc("a", "b"));
// console.log(myFunc("a",1)); // Error 函数接口已经做了限制

// ----------------------类类型-实现接口---------------------- //
// 严格意义上构造函数不属于成员,只是为了在实例化的时候初始化一些成员,不必剑走偏锋,想要将构造函数constructor 进行限制~~~
// 类实现接口,所限制的只是类实现了接口的类型限制,就是类可以有自己的一些自定义的成员
// 如下方类Clock,接口是ClockInterface
// 1. currentTime变量成员:接口有了,则类要有声明,赋值可以用表达式
// 2. setTime函数成员:接口有了,则类要有声明,赋值可以在构造函数
// 3. getDay函数成员:接口中是可选属性,则类中可有可无
// 4. getDate函数函数:接口中没有,则类中可有可无
console.log("----------------------类类型-实现implements--------------------");
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
  // 这边也可以设置可选属性~
  getDay?(): number;
  // 这里注释了也没问题,因为类实现接口只需满足 接口存在的类型限制
  // 接口描述了类的部分类型限制
  // getDate(): number;
}

class Clock implements ClockInterface {
  // 当前时间
  // 初始化表达式赋值...
  currentTime = new Date();
  // 设置时间
  setTime;
  // 这边是可选属性,注释了也没问题~
  // getDay = () => {
  //   return this.currentTime.getDay();
  // };
  // 类中可以自定义成员,不需要被类类型的接口限制死
  // 获取当前几号
  getDate = () => {
    return this.currentTime.getDate();
  };
  constructor(h: number, m: number) {
    // 在构造函数中赋值...
    this.setTime = (d: Date) => {
      this.currentTime = d;
    };
  }
}

// ----------------------继承接口---------------------- //
// 接口的继承也是用关键字extends
console.log("----------------------继承接口--------------------");

// 定义一个 "形状" 接口,附加颜色通用属性
interface Shape {
  color: string;
}
// 定义一个 "正方形" 接口,他继承了 "形状" 接口,并且带了边长属性
interface Square extends Shape {
  sideLength: number;
}

// 调用方式1. 断言1
// TypeScript 断言 <Type>
// 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。
// 类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;

// 调用方式1. 断言2  as 语法
let square1 = {} as Square;
square1.color = "red";
square1.sideLength = 12;

// 在赋值前不能使用了变量“square2”。
let square2: Square;
// square2.color = "green";  // Error  在赋值前使用了变量“square1”
// square2.sideLength = 100;  // Error  在赋值前使用了变量“square1”

// ----------------------类类型-接口继承类---------------------- //
// 具体的class将会专门做一篇文章~~~
// 接口继承类也是用关键字extends
// 场景:已经有现成的父类Father,父类的接口实现是FatherInterface
// 思考1:如果只是想单纯继承FatherInterface 就是上一节的内容。
// 思考2:如果想要继承Father里面所有的属性呢?FatherInterface只是对父类的部分成员进行了类型限制!!!
// console.log("--------------------类类型-接口继承类--------------------");

// 先来初始化父类
// 父类接口 FatherInterface
interface FatherInterface {
  // 只对name成员进行了类型限制
  name: string;
  getName(): string;
}
// 父类 已经实现接口
class Father implements FatherInterface {
  name;
  getName = () => {
    return "Father:" + this.name;
  };
  // 这边添加了一个成员,跟FatherInterface区分开
  // 等下看看继承Father的接口是否也继承了sex的类型限制
  sex;
  constructor(name: string, sex: string) {
    this.name = name;
    this.sex = sex;
  }
}

// 想要继承FatherInterface                          |  用接口继承接口即可
// 想要继承Father中的所有成员                       | 用类继承类
// 只要Fahter中的所有成员类型限制,所有的成员自己赋值|  接口继承类

// 针对:只要Father中的所有成员类型限制,所有的成员自己赋值
// 继承父类所有的类型,不止FatherInterface接口中的name,还有Father类中sex
// 这边只是对接口进行了继承,不是对类进行继承,所以不会得到Father成员实现,只有声明~
// 所以需要重新实现
// 子类接口 Father1Interface  对age进行了类型限制
interface Father1Interface extends Father {
  age: number;
}
class Father1 implements Father1Interface  {
  //  只是继承了类的类型限制,这边需要重新声明
  name;
  //  只是继承了类的类型限制,这边需要重新声明
  sex;
  //  只是继承了类的类型限制,需要对其进行声明并且实现
  getName = () => {
    return "Father1:" + this.name;
  };
  //  普通接口成员声明
  age;
  constructor(name: string, sex: string, age: number) {
    // 只是继承了类的类型限制,这边需要重新实现
    this.name = name;
    // 只是继承了类的类型限制,这边需要重新实现
    this.sex = sex;
    // 普通接口成员实现
    this.age = age;
  }
}

// new Father1 实例化一个对象,father1
let father1 = new Father1("另外的爸爸:", "男", 68);
console.log(
  father1.getName()
);

// 这边可以弄个类继承类对比下
class Children extends Father {
  age: number;
  constructor(name: string, sex: string, age: number) {
    super(name, sex);
    this.age = age;
  }
}
let children = new Children("儿子", "男", 18);
console.log(
  // 子类没有重新实现,则可以调用父类的getName()
  children.getName()
);

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Penk是个码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值