TypeScript中extends的正确打开方式详解
作者:kennyshaw 发布时间:2024-02-25 07:14:18
前言
最近完整地看了一遍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
必须要有类Animal
的name
属性以及实现eat
方法,并且还要实现本身的miemie
方法。但是我们不必实现类Animal
的静态方法run
。
是不是觉得还会有下一个标题类继承接口
,对不起,这个真没有!类继承接口使用的关键字变成了implements
。
extends第二式:三元表达式条件判断
extends
还有一个比较常见的用法就是在三元表达式中进行条件判断,即判断一个类型是否可以分配给另一个类型。这里根据三元表达式中是否存在泛型判断结果还不一致,首先我们介绍普通的三元表达式条件判断。
普通的三元表达式条件判断
带有extends
的三元表达式如下:
type TypeRes=Type1 extends Type2? Type3: Type4;
这里表达的意思就是如果类型Type1
可被分配给类型Type2
,则类型TypeRes
取Type3
,否则取Type4
。那怎么理解类型Type1
可被分配给类型Type2
呢??
我们可以这样理解:类型为Type1
的值可被赋值给类型为Type2
的变量。可以具体分为一下几种情况:
Type1
和Type2
为同一种类型。Type1
是Type2
的子类型。Type2
类型兼容类型Type1
。 接下来我们分情况进行验证。
情况一:Type1
和Type2
为同一种类型。
type Type1=string;
type Type2=Type1;
type Type3=true;
type Type4=false;
type TypeRes=Type1 extends Type2? Type3: Type4;
// true
这里Type1
和Type2
为同一种类型。因此Type1
可被分配给Type2
。因此TypeRes
类型最后取为true
。
情况二:Type1
是Type2
的子类型。
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
,即Type1
是Type2
的子类型。因此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;
没错,由于Type1
是Type2
的父类型,因此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
进行了约束,其必须至少要拥有ISheep
的name
属性及eat
、miemie
方法,另外T
中若有其他的属性及方法,则不作限制。这里我们便通过extends
对泛型T
进行了约束。
其实泛型约束中的extends
也是起到了三元表达式中类型分配的作用,其中T extends ISheep
表示泛型T
必须可以分配给类型Isheep
。
来源:https://juejin.cn/post/7133893150837309476
猜你喜欢
- 前言写爬虫有一个绕不过去的问题就是验证码,现在验证码分类大概有4种:图像类滑动类点击类语音类今天先来看看图像类,这类验证码大多是数字、字母的
- SQL Server数据库连接中常见的错误分析:一."SQL Server 不存在或访问被拒绝"这个是最复杂的,错误发生
- remove 删除单个元素,删除首个符合条件的元素,按值删除,返回值为空List_remove = [1, 2, 2, 2, 3, 4]pr
- 一、Tensorlow结构import tensorflow as tfimport numpy as np#创建数据x_data = np
- 从ResNet到DenseNet上图中,左边是ResNet,右边是DenseNet,它们在跨层上的主要区别是:使用相加和使用连结。最后,将这
- 虽然有很多种方式可以解决这个问题,但是我们可以用T-SQL代码来处理这个文件删除过程。我用xp_cmdshell命令和FORFILES命令来
- 1.列表元素删操作的方法列表的删操作指的是在列表中删除已存在的元素,列表中的元素被删除后,后面所有的元素依次往前移动一位,挂在被删除元素的索
- 1.sonarqube是一款代码分析的工具,通过soanrScanner扫描后的数据传递给sonarqube进行分析2.sonarqube社
- CSSer与其他IT职位一样,在找工作的时候,都会面临着面试官提出的问题,或者给出的试卷。一、超链接点击过后hover样式就不出现的问题?被
- 问题产生:今天在编写神经网络的Cluster作业时,需要根据根据数据标签用不同的颜色画出数据的分布情况,由此学习到了这种高效的方法。传统思路
- windows server 2016 与 sql server 2016 都可用允许不许要加入AD ,管理方面省了挺多操作,也不用担心域控
- 开篇先明义:lambda是表达式,而def函数是语句代码块所以lambda其实就是一个稍微高级一点的式子而已,只不过这个式子比较长,而且还会
- time()在PHP中是得到一个数字,这个数字表示从1970-01-01到现在共走了多少秒,很奇怪吧 不过这样方便计算, 要找出前一天的时间
- 序列解包(Sequence Unpacking)是Python中非常重要和常用的一个功能,可以使用非常简洁的形式完成复杂的功能,大幅度提高了
- 案例以论坛为例,有个接口返回帖子(posts)信息,然后呢,来了新需求,说需要显示帖子的 author 信息。此时会有两种选择:在 post
- 今天在写 mysql 遇到一个比较特殊的问题。 mysql 语句如下: update wms_cabinet_form set cabf_e
- 昨天在网上看到一个防采集软件,说采集只访问当前网页,不会访问网页的图片、JS等,今天突然想到,通过动态程序和Js访问分别记录访问者的IP,然
- 在开发过程中,有时遇到由于缓存问题导致页面不能及时更新,有时页面引入了不必需的样式脚本文件,有时由于文件太多,字节过大导致页面的性能缓慢,为
- 1.介绍当我们使用pytorch来构建网络框架的时候,也会遇到和tensorflow(tensorflow __init__、build 和
- Python中可以用docx来生成word文档,docx中可以自定义文字的大小和字体等。其中要整体修改文字的字体大小和字体,可以用以下方法: