当前位置 : 主页 > 网络编程 > JavaScript >

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

来源:互联网 收集:自由互联 发布时间:2023-02-08
目录 前言 extends第一式:继承 类继承类 接口继承接口 接口继承类 extends第二式:三元表达式条件判断 普通的三元表达式条件判断 情况一:Type1和Type2为同一种类型。 情况二:Type1是T
目录
  • 前言
  • 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打开方式的资料请关注易盾网络其它相关文章!

      上一篇:EasyUI使用DataGrid实现动态列数据绑定
      下一篇:没有了
      网友评论