TypeScript中extends的正确打开方式详解

目录
  • 前言
  • extends第一式:继承
    • 类继承类
    • 接口继承接口
    • 接口继承类
  • extends第二式:三元表达式条件判断
    • 普通的三元表达式条件判断
      • 情况一:Type1和Type2为同一种类型。
      • 情况二:Type1是Type2的子类型。
      • 情况三: Type2类型兼容类型Type1。
    • 带有泛型的三元表达式条件判断
  • extends第三式:泛型约束

前言

最近完整地看了一遍TypeScript的官方文档,发现文档中有一些知识点没有专门讲解到,或者是讲解了但却十分难以理解,因此就有了这一系列的文章,我将对没有讲解到的或者是我认为难以理解的知识点进行补充讲解,希望能给您带来一点帮助。

tips:配合官方文档食用更佳

这是本系列的第二篇TypeScript中extends的正确打开方式,在TypeScript中我们经常见到extends这一关键字,我们可能第一时间想到的就是继承,但除了继承它其实还有其他的用法,接下来让我们来一一获得extends的正确打开方式。

extends第一式:继承

作为众人皆知的extends关键字,其最出名的使用方式便是继承。

类继承类

首先让我们来用extends实现一下类的继承。

class Animal {
  public name;
  constructor(name: string) {
    this.name = name;
  }
  eat(food: string) {
    console.log(`${this.name}正在吃${food}`);
  }
}
class Sheep extends Animal {
  constructor(name: string) {
    super(name);
  }
  miemie() {
    console.log("别看我只是一只羊,羊儿的聪明难以想象~");
  }
}
let lanyangyang = new Sheep("懒羊羊");
lanyangyang.eat("青草蛋糕");
// 懒羊羊正在吃青草蛋糕
lanyangyang.miemie();
//别看我只是一只羊,羊儿的聪明难以想象~

首先我们定义了一个Animal类,该类有name属性以及eat方法。然后又定义了一个继承Animal类的Sheep类,该类在父类name属性以及eat方法基础上又新增了一个miemie方法。

接口继承接口

extends不仅能够用于类与类之间的继承上,还能够用于接口与接口之间的继承。接下来我们来实现一下接口之间的继承。

interface IAnimal{
  name:string;
  eat:(food:string)=>void;
}
interface ISheep extends IAnimal{
  miemie:()=>void;
}
let lanyangyang:ISheep={
  name:'懒羊羊',
  eat(food:string){
    console.log(`${this.name}正在吃${food}`);
  },
  miemie() {
    console.log("别看我只是一只羊,羊儿的聪明难以想象~");
  }
}
lanyangyang.eat("青草蛋糕");
// 懒羊羊正在吃青草蛋糕
lanyangyang.miemie();
//别看我只是一只羊,羊儿的聪明难以想象~

我们定义了一个IAnimal接口,然后用通过extends继承IAnimal定义了ISheep接口,则实现ISheep接口的变量lanyangyang必须要有父接口的name属性以及实现eat方法,并且还要实现本身的miemie方法。

现在我们通过extends实现了类与类之间的继承、接口与接口之间的继承,那么类与接口之间是否能互相继承呢?答案是可以。

接口继承类

首先我们使用extends来实现接口继承类。

class Animal {
  public name;
  constructor(name: string) {
    this.name = name;
  }
  eat(food: string) {
    console.log(`${this.name}正在吃${food}`);
  }
  static run(){
    console.log(`${this.name} is running`)
  }
}
interface ISheep extends Animal{
  miemie:()=>void;
}
let lanyangyang:ISheep={
  name:'懒羊羊',
  eat(food:string){
    console.log(`${this.name}正在吃${food}`);
  },
  miemie() {
    console.log("别看我只是一只羊,羊儿的聪明难以想象~");
  }
}
lanyangyang.eat("青草蛋糕");
// 懒羊羊正在吃青草蛋糕
lanyangyang.miemie();
//别看我只是一只羊,羊儿的聪明难以想象~

在接口继承类时,可以把类看作一个接口,但是类中的静态方法是不会继承过来的。我们在实现ISheep接口的变量lanyangyang必须要有类Animalname属性以及实现eat方法,并且还要实现本身的miemie方法。但是我们不必实现类Animal的静态方法run

是不是觉得还会有下一个标题类继承接口,对不起,这个真没有!类继承接口使用的关键字变成了implements

extends第二式:三元表达式条件判断

extends还有一个比较常见的用法就是在三元表达式中进行条件判断,即判断一个类型是否可以分配给另一个类型。这里根据三元表达式中是否存在泛型判断结果还不一致,首先我们介绍普通的三元表达式条件判断。

普通的三元表达式条件判断

带有extends的三元表达式如下:

type TypeRes=Type1 extends Type2? Type3: Type4;

这里表达的意思就是如果类型Type1可被分配给类型Type2,则类型TypeResType3,否则取Type4。那怎么理解类型Type1可被分配给类型Type2呢??

我们可以这样理解:类型为Type1的值可被赋值给类型为Type2的变量。可以具体分为一下几种情况:

  • Type1Type2为同一种类型。
  • Type1Type2的子类型。
  • Type2类型兼容类型Type1。 接下来我们分情况进行验证。

情况一:Type1Type2为同一种类型。

type Type1=string;
type Type2=Type1;
type Type3=true;
type Type4=false;
type TypeRes=Type1 extends Type2? Type3: Type4;
// true

这里Type1Type2为同一种类型。因此Type1可被分配给Type2。因此TypeRes类型最后取为true

情况二:Type1Type2的子类型。

class Animal {
  public name;
  constructor(name: string) {
    this.name = name;
  }
  eat(food: string) {
    console.log(`${this.name}正在吃${food}`);
  }
}
class Sheep extends Animal {
  constructor(name: string) {
    super(name);
  }
  miemie() {
    console.log("别看我只是一只羊,羊儿的聪明难以想象~");
  }
}
type Type1=Sheep;
type Type2=Animal;
type Type3=true;
type Type4=false;
type TypeRes=Type1 extends Type2? Type3: Type4;
// true

这里Sheep类继承自Animal,即Type1Type2的子类型。因此Type1可被分配给Type2。因此TypeRes类型最后取为true

情况三: Type2类型兼容类型Type1

首先还是抛出一个问题,什么是类型兼容??这个问题可以从官方文档中得到答案,大家可以戳类型兼容性详细了解!

所谓 Type2类型兼容类型Type1,指得就是Type1类型的值可被赋值给类型为Type2的变量。 举个栗子:

type Type1={
  name:string;
  age:number;
  gender:string;
}
type Type2={
  name:string;
  age:number;
}
type Type3=true;
type Type4=false;
type TypeRes=Type1 extends Type2? Type3: Type4;
// true

由于类型Type1拥有至少与Type2相同的属性,因此Type2是兼容Type1的。也就是说Type1类型的值可被赋值给类型为Type2的变量。

let kenny1:Type1={
  name:'kenny',
  age:26,
  gender:'male'
}
let kenny2:Type2=kenny;
// no Error

因此TypeRes类型最后取为true

再举个栗子,以函数的兼容性为例,

type Type1=(a:number)=>void;
type Type2=(a:number,b:string)=>void;
type Type3=true;
type Type4=false;
type TypeRes=Type1 extends Type2? Type3: Type4;
// true

当函数参数不同时,看Type2是否兼容Type1,就要看Type1的每个参数必须能在Type2里找到对应类型的参数。 注意的是参数的名字相同与否无所谓,只看它们的类型。

这里Type1第一个number类型的参数是可以在Type2中找到,即Type1类型的函数可被赋值给类型为Type2的变量。

let fn1:Type1=(a:number)=>{}
let kenny2:Type2=fn1;
// no Error

因此TypeRes类型最后取为true

带有泛型的三元表达式条件判断

我们先来一个举一个不带泛型的栗子,大家可以想想结果是什么。

type Type1=string|number;
type Type2=string;
type Type3=true;
type Type4=false;
type TypeRes=Type1 extends Type2? Type3: Type4;

没错,由于Type1Type2的父类型,因此Type1是不可分配给Type2的,因此TypeRes类型最后取为false

但当我们加上泛型之后呢,再来看一个栗子。

type Type1=string|number;
type Type2=string;
type Type3=true;
type Type4=false;
type Type5<T>=T extends Type2? Type3: Type4;
type TypeRes<Type1>
// boolean

这里TypeRes类型最后就不是false了,而变成boolean。这是为什么呢?

原来再使用泛型时,若extends左侧的泛型具体取为一个联合类型时,就会把联合类型中的类型拆开,分别带入到条件判断式中进行判断,最后把结果再进行联合。上述的栗子中结果可以这么来看,

(string extends string?true:false)|(number extends string?true:false)
true | false
boolean

在高级类型中有很多类型实现便用到了这一特性。

比如Exclude<T, U> -- 从T中剔除可以赋值给U的类型。

type T1 = "a" | "b" | "c" | "d";
type T2 = "a" | "c" | "f"
type ExcludeT1T2=Exclude<T1,T2> //"b"|"d"

该类型的类型实现为

type Exclude<T, U> = T extends U ? never : T;

T为联合类型时,会自动分发条件,对T中的所有类型进行遍历,判断其是否可以分配给类型U,如果是的话便返回never类型,否则返回其原来的类型。最后再将其进行联合得到一个结果联合类型。

由于never类型与其他类型联合最终得到的还是其他类型,因此便可以从类型T中剔除掉可以赋给U的类型。

那有没有办法让泛型三元表达式中extends和普通的extends作用相同?有!只需要给泛型加一个[]。栗子如下:

type Type1=string|number;
type Type2=string;
type Type3=true;
type Type4=false;
type Type5<T>=[T] extends Type2? Type3: Type4;
type TypeRes<Type1>
// false

extends第三式:泛型约束

首先我们来回答一下什么是泛型?简单来说,泛型就是一种类型变量,普通的变量代表一个任意的值,而不是一个特定的值,我们可以把任何值赋给变量,而类型变量代表一个任意的类型,而不是一个特定的类型,我们可以把任何类型赋给类型变量。它是一种特殊的变量,只用于表示类型而不是值。

那如果我们不想让泛型表示任意类型时,该怎么办?这时我们就可以使用extends对泛型进行约束,让泛型表示满足一定条件的类型。接下来,我们使用extends进行泛型的约束。

interface ISheep{
  name:string;
  eat:(food:string)=>void;
  miemie:()=>void;
}
function eatAndMiemie<T extends ISheep>(sheep:T):void{
    sheep.eat("青草蛋糕");
    sheep.miemie();
}
eatAndMiemie(
{
  name: "懒羊羊",
  eat(food:string){
    console.log(`${this.name}正在吃${food}`);
  },
  miemie() {
    console.log("别看我只是一只羊,羊儿的聪明难以想象~");
  }
  run() {console.log(`${this.name}正在奔跑`)};
  }
)
// 懒羊羊正在吃青草蛋糕
//别看我只是一只羊,羊儿的聪明难以想象~

这里我们便对泛型T进行了约束,其必须至少要拥有ISheepname属性及eatmiemie方法,另外T中若有其他的属性及方法,则不作限制。这里我们便通过extends对泛型T进行了约束。

其实泛型约束中的extends也是起到了三元表达式中类型分配的作用,其中T extends ISheep表示泛型T必须可以分配给类型Isheep

以上就是TypeScript中extends的正确打开方式详解的详细内容,更多关于TypeScript extends打开方式的资料请关注我们其它相关文章!

(0)

相关推荐

  • Typescript中extends关键字的基本使用

    目录 前言 基本使用 泛型约束 条件类型与高阶类型 在高级类型中的应用 参考文献 总结 前言 extends关键字在TS编程中出现的频率挺高的,而且不同场景下代表的含义不一样,特此总结一下: 表示继承/拓展的含义 表示约束的含义 表示分配的含义 基本使用 extends是 ts 里一个很常见的关键字,同时也是 es6 里引入的一个新的关键字.在 js 里,extends一般和class一起使用,例如: 继承父类的方法和属性 class Animal { kind = 'animal' const

  • JavaScript进阶教程之非extends的组合继承详解

    目录 前言 一:call() 的作用与使用 1.1 使用 call() 来调用函数 1.2 使用 call() 来改变 this 的指向 二:利用构造函数继承父属性 2.1 实现过程 2.1 实现过程分析 三:利用原型对象继承父方法 3.1 继承父方法的错误演示 3.2 继承父方法的正确做法 3.2 继承父方法的注意事项 总结 前言 继承也是面向对象的特性之一,但是在 ES6 版本之前是没有 extends 去实现继承的,我们只能通过 构造函数 和 原型对象 来实现继承,其中分别为构造函数来继承

  • TypeScript新语法之infer extends示例详解

    目录 正文 模式匹配 提取枚举的值的类型 总结 正文 我们知道,TypeScript 支持 infer 来提取类型的一部分,通过模式匹配的方式. 模式匹配 比如元组类型提取最后一个元素的类型: type Last<Arr extends unknown[]> = Arr extends [...infer rest,infer Ele] ? Ele : never; 比如函数提取返回值类型: type GetReturnType<Func extends Function> = F

  • 详解JAVA中implement和extends的区别

    详解JAVA中implement和extends的区别 extends是继承父类,只要那个类不是声明为final或者那个类定义为abstract的就能继承,Java中不支持多重继承,但是可以用接口来实现,这样就要用到implements,继承只能继承一个类,但implements可以实现多个接口,用逗号分开就行了比如class A extends B implements C,D,E implements是一个类实现一个接口用的关键字,他是用来实现接口中定义的抽象方法. 还有几点需要注意: (1

  • TypeScript中extends的正确打开方式详解

    目录 前言 extends第一式:继承 类继承类 接口继承接口 接口继承类 extends第二式:三元表达式条件判断 普通的三元表达式条件判断 情况一:Type1和Type2为同一种类型. 情况二:Type1是Type2的子类型. 情况三: Type2类型兼容类型Type1. 带有泛型的三元表达式条件判断 extends第三式:泛型约束 前言 最近完整地看了一遍TypeScript的官方文档,发现文档中有一些知识点没有专门讲解到,或者是讲解了但却十分难以理解,因此就有了这一系列的文章,我将对没有

  • python中四舍五入的正确打开方式

    round()函数 (注意:下面的我也不清楚是否正确,我只是发表一下我的观点) 对于简单的舍入运算,使用内置的 round(value, ndigits) 函数即可 强烈建议不要去深究,就直接得结果就好 ndigiths可以为正数,也可以为负数,还可以为0,可以为空 n:就是精确到第n位小数,对整数没有影响,1为精确到十分位(注意:小数就是从十分位往后推的) -n:就是精确到整数位,-1为精确到十位,然后就是百位千位-有小数位就全舍掉,不管多大,但会保留一个为0的小数位 0:精确到个位,但会保留

  • Spring之@Aspect中通知的5种方式详解

    目录 @Before:前置通知 案例 对应的通知类 通知中获取被调方法信息 JoinPoint:连接点信息 ProceedingJoinPoint:环绕通知连接点信息 Signature:连接点签名信息 @Around:环绕通知 介绍 特点 案例 对应的通知类 @After:后置通知 介绍 特点 对应的通知类 @AfterReturning:返回通知 用法 特点 案例 对应的通知类 @AfterThrowing:异常通知 用法 特点 案例 对应的通知类 几种通知对比 @Aspect中有5种通知

  • C语言中进程间通讯的方式详解

    目录 一.无名管道 1.1无名管道的原理 1.2功能 1.3无名管道通信特点 1.4无名管道的实例 二.有名管道 2.1有名管道的原理 2.2有名管道的特点 2.3有名管道实例 三.信号 3.1信号的概念 3.2发送信号的函数 3.3常用的信号 3.4实例 四.IPC进程间通信 4.1IPC进程间通信的种类 4.2查看IPC进程间通信的命令 4.3消息队列 4.4共享内存 4.5信号灯集合 一.无名管道 1.1无名管道的原理 无名管道只能用于亲缘间进程的通信,无名管道的大小是64K.无名管道是内

  • Node.js中参数传递的两种方式详解

    目录 参数传递方式 GET方式 POST方式 动态网页 参数传递方式 在Node.js中,参数传递常见的共两种方式: GET方式:通过地址栏键=值的方式进行传递. POST方式:通过表单的方式传递请求数据. GET方式 GET方式通常是在请求地址中以[?参数1=值1&参数2=值2]的格式进行传递,在Node.js中可以通过获取url然后进行获取参数,如下所示: //1.引入http模块 var http = require('http'); //2.创建服务 var server = http.

  • Vue-CLI项目中路由传参的方式详解

    一.标签传参方式:<router-link></router-link> 第一种 router.js { path: '/course/detail/:pk', name: 'course-detail', component: CourseDetail } 传递层 <!-- card的内容 { id: 1, bgColor: 'red', title: 'Python基础' } --> <router-link :to="`/course/detail

  • Mybatis中使用in()查询的方式详解

    目录 1 使用数组方式 2 使用List集合的方式 3 第三种我们使用Mybatis-plus框架的条件构造器来进行查询 附:Mybatis-plus的条件构造器详细使用教程 总结 这篇文章我会演示几种mybatis中使用in查询的方式. 1 数组.字符串 2 集合 3 使用Myabtis-plus框架的条件构造器来实现 我们在mysql中使用in查询的方式是这样的 那在mybatis中我们使用<foreach>标签来实现包含查询 1 使用数组方式 Mapper: Mapper.xml: &l

  • .NET Core中HttpClient的正确打开方式

    前言 在 Asp.Net Core 1.0 时代,由于设计上的问题, HttpClient 给开发者带来了无尽的困扰,用 Asp.Net Core 开发团队的话来说就是:我们注意到,HttpClient 被很多开发人员不正确的使用.得益于 .Net Core 不断的版本快速升级: 问题来源 长期以来,.NET开发者都通过下面的方式发送http请求: using (var httpClient = new HttpClient()) { var response = await httpClien

  • Python中调用其他程序的方式详解

    前言 在Python中,可以方便地使用os模块来运行其他脚本或者程序,这样就可以在脚本中直接使用其他脚本或程序提供的功能,而不必再次编写实现该功能的代码.为了更好地控制运行的进程, 可以使用win32process模块中的函数,如果想进一步控制进程,则可以使用ctype模块,直接调用kernel32.dll中的函数.下面介绍4种方式: 1.os.system()函数 os模块中的system()函数可以方便地运行其他程序或者脚本,模式如下: os.system(command):command:

  • Java Redis分布式锁的正确实现方式详解

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁. 可靠性 首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 互斥性.在任意时刻,只有一个客户端能持有锁. 不会发生死锁.即使有一个客户端在

随机推荐