• TypeScript接口与泛型的使用


    (一)TypeScript接口

    1.声明一个对象类型

    // 通过类型(type)别名声明
    type InfoType = {name: string, age: number}
    
    • 1
    • 2

    接口interface,并且可以定义可选类型(?),也可以定义只读属性(readonly )

    interface IInfoType {
      readonly name: string
      age: number
      friend?: {
        name: string
      }
    }
    const info: IInfoType = {
      name: "why",
      age: 18,
      friend: {
        name: "kobe"
      }
    }
    console.log(info.friend?.name)
    console.log(info.name)
    info.age = 20
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.索引类型

    当我们的对象的key,value不确定的时候,我们可以:

    // 通过interface来定义索引类型
    interface IndexLanguage {
      [index: number]: string
    }
    
    const frontLanguage: IndexLanguage = {
      0: "HTML",
      1: "CSS",
      2: "JavaScript",
      3: "Vue"
    }
    interface ILanguageYear {
      [name: string]: number
    }
    const languageYear: ILanguageYear = {
      "C": 1972,
      "Java": 1995,
      "JavaScript": 1996,
      "TypeScript": 2014
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.函数的类型

    函数的类型也可通过interface的方式定义,但是一般都建议使用type的方式进行定义;

    // type CalcFn = (n1: number, n2: number) => number
    // 可调用的接口
    interface CalcFn {
      (n1: number, n2: number): number
    }
    function calc(num1: number, num2: number, calcFn: CalcFn) {
      return calcFn(num1, num2)
    }
    const add: CalcFn = (num1, num2) => {
      return num1 + num2
    }
    calc(20, 30, add)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4.接口的继承

    接口interface的继承与类class类似,都是使用关键字extends ,并且接口可实现多继承(类不支持)

    interface ISwim {
      swimming: () => void
    }
    
    interface IFly {
      flying: () => void
    }
    interface IAction extends ISwim, IFly {
    }
    const action: IAction = {
      swimming() {
      },
      flying() {
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.交叉类型

    继联合类型的使用:

    // 联合类型
    type WhyType = number | string
    type Direction = "left" | "right" | "center"
    
    • 1
    • 2
    • 3

    另外一种类型的方法叫交叉类型(Intersection Types):

    type WType = number & string;
    
    • 1

    但不存在一个变量即满足number,又是一个string的值,所以在开发中通常对对象类型进行交叉;

    //交叉类型
    interface ISwim {
      swimming: () => void
    }
    interface IFly {
      flying: () => void
    }
    type MyType1 = ISwim | IFly
    type MyType2 = ISwim & IFly
    const obj1: MyType1 = {
      flying() {
      }
    }
    const obj2: MyType2 = {
      swimming() {
      },
      flying() {
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    6.接口的实现

    interface ISwim {
      swimming: () => void
    }
    interface IEat {
      eating: () => void
    }
    // 类实现接口
    class Animal {
    }
    // 继承: 只能实现单继承
    // 实现: 实现接口, 类可以实现多个接口
    class Fish extends Animal implements ISwim, IEat {
      swimming() {
        console.log("Fish Swmming")
      }
      eating() {
        console.log("Fish Eating")
      }
    }
    class Person implements ISwim {
      swimming() {
        console.log("Person Swimming")
      }
    }
    // 编写一些公共的API: 面向接口编程
    function swimAction(swimable: ISwim) {
      swimable.swimming()
    }
    // 1.所有实现了接口的类对应的对象, 都是可以传入
    swimAction(new Fish())
    swimAction(new Person())
    swimAction({swimming: function() {}})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    interface和type区别

    我们时常会困惑interfacetype用来定义对象类型有什么不同,该怎么去选择?

    • 定义非对象类型,通常推荐使用type,比如Direction、Alignment、一些Function;
    • 定义对象类型,那么他们是有区别的:
      interface 可以重复的对某个接口来定义属性和方法;
      type定义的是别名,别名是不能重复的;
      在这里插入图片描述

    7.字面量的赋值(freshness擦除)

    将一个变量标识符赋值给其他的变量时,会进行freshness擦除操作:

    interface IPerson {
      name: string
      age: number
      height: number
    }
    function printInfo(person: IPerson) {
    	console.log(person);
    }
    // 代码在编译的时候就会直接的报错,不存在address属性
    printInfo({
      name: "why",
      age: 18,
      height: 1.88,
      address: "广州市",
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    类型检测不通过
    请添加图片描述
    而这里只使用自己定义好的值,多余的值会进行freshness擦除后进行类型检测,并且通过ts的类型检测,

    // ts的检测会自动的推倒出一个info的字面量类型
    // 并且具备有address的属性,将其赋值到printInfo()中就会将address擦除掉;
    const info = {
      name: "wendy",
      age: 18,
      height: 1.88,
      address: "深圳市"
    }
    // 赋值的是对象的引用,会进行属性的擦除
    printInfo(info); 
    // {name:'wendy',age: 18, height: 1.88, address: '深圳市'}
    // 并且是无法取出address的值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    8.枚举类型

    枚举是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型;
    枚举类型的使用:允许开发者定义一组命名常量,常量可以是数字number、字符串类型string;如下:

    // type Direction = "left" | "Right" | "Top" | "Bottom"
    // 枚举的类型一般都是大写,字符串的常量
    enum Direction {
      LEFT,
      RIGHT,
      TOP,
      BOTTOM
    }
    // 枚举类似与一个数字的常量,等同于:
    // enum Direction {
    //   LEFT = 0,
    //   RIGHT = 1,
    //   TOP = 2,
    //   BOTTOM =3
    // }
    function turnDirection(direction: Direction) {
      switch (direction) {
        case Direction.LEFT:
          console.log("改变角色的方向向左")
          break;
        case Direction.RIGHT:
          console.log("改变角色的方向向右")
          break;
        case Direction.TOP:
          console.log("改变角色的方向向上")
          break;
        case Direction.BOTTOM:
          console.log("改变角色的方向向下")
          break;
        default:
          const foo: never = direction;
          break;
      }
    }
    turnDirection(Direction.LEFT)
    turnDirection(Direction.RIGHT)
    turnDirection(Direction.TOP)
    turnDirection(Direction.BOTTOM)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    枚举类型默认是有值的,比如上面的枚举,默认值是这样的:

    enum Direction {
      LEFT = "LEFT",
      RIGHT = "RIGHT",
      TOP = "TOP",
      BOTTOM = "BOTTOM"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    当然,我们也可以给枚举其他值,比如这个时候会从100进行递增;

    enum Direction {
      LEFT = 100,
      RIGHT,
      TOP,
      BOTTOM
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (二)TypeScript泛型的使用

    代码的构建不仅仅是规范与严谨性,还希望代码具有复用性,就好比我们封装一些API时,通过传入不同参数执行不同的事件,但对于参数的类型是否也可以参数化;这个就叫做类型的参数化

    1.泛型的基本使用

    类似于:封装一个函数,传入一个参数,并返回这个参数;

    // 返回的数据的类型是一致
    function fun(mes: string):string {
      return mes;
    }
    
    • 1
    • 2
    • 3
    • 4

    上面的代码虽然实现返回的类型一致,但是却无法适用于其他的类型,只是在此固定为string的类型;

    // any的类型即将丢失类型的信息,与最先无定义的无差别
    function fun(mes: any):any {
      return mes;
    }
    
    • 1
    • 2
    • 3
    • 4

    我们在这里使用特殊的变量-类型变量(type variable),它作用于类型,而不是值;

    // 在定义这个函数时, 我不决定这些参数的类型
    // 而是让调用者以参数的形式告知,我这里的函数参数应该是什么类型
    function sum<Type>(num: Type): Type {
      return num
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里我们可以使用两种方式来调用它:

    • 方式一:通过 <类型> 的方式将类型传递给函数;
      // 明确的传入类型
      sum<number>(20)
      sum<{name: string}>({name: "why"})
      sum<any[]>(["abc"])
      
      • 1
      • 2
      • 3
      • 4
    • 方式二:通过类型推到,自动推到出我们传入变量的类型:
      // 调用方式二: 类型推导
      sum(50);
      sum("abc");
      
      • 1
      • 2
      • 3

    2.泛型可传入多个参数

    function foo<T, E, O>(arg1: T, arg2: E, arg3?: O, ...args: T[]) {
    }
    foo<number, string, boolean>(10, "abc", true)
    
    • 1
    • 2
    • 3

    并且我们在平常的开发当前,经常使用到名称缩写:

    • T: Type缩写;
    • K,V: key和value的缩写,键值对;
    • E: Element的缩写,元素;
    • O: Object的缩写,对象;

    3.泛型的接口使用

    interface IPerson<T> {
      id: T
      numList: T[],
      getID:( vallue: T) => void;
    }
    
    const p: IPerson<number> = {
      id: 1,
      numList: [99, 10, 10],
      getID: function(id: number) {
        console.log(id)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4.泛型的类使用

    定义一个泛型类的使用:

    class Point<T> {
      x: T
      y: T
      z: T
      constructor(x: T, y: T, z: T) {
        this.x = x
        this.y = y
        this.z = y
      }
    }
    const p1 = new Point("1.33.2", "2.22.3", "4.22.1")
    const p2 = new Point<string>("1.33.2", "2.22.3", "4.22.1")
    const p3: Point<string> = new Point("1.33.2", "2.22.3", "4.22.1")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5.泛型的约束

    我们有个需求是希望传入的类型有某些共性,但共性可不在同一类型当中:
    就好比我们希望传入的类型都有length的属性,所以该类型可能是stringarray某些对象;那如果这里我们要求只要是具备length的属性的都可作为参数类型,这个应该如何操作:

    interface ILength {
      length: number
    }
    function getLength<T extends ILength>(arg: T) {
      return arg.length
    }
    getLength("abc")
    getLength(["abc", "cba"])
    getLength({length: 100})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (三)TypeScript的作用域

    TypeScript支持两种方式来控制我们的作用域:

    • 模块化:每个文件可以是一个独立的模块,支持ES Module,也支持CommonJS
      export function add(num1: number, num2: number) {
          return num1 + num2;
      }
      export function sub(num1: number, num2: number) {
          return num1 - num2;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 命名空间:通过namespace来声明一个命名空间
      早期时被称为"内部模块",主要将模块的内部进行作用域的划分,防止一些命名的冲突问题;
      export namespace Time {
         export function format(time: string) {
              return '2022-07-05';
          }
      }
      // 同个方法名称不同的命名空间中定义
      export namespace Price {
          export function format(time: string) {
              return '20.22';
          }
      }
      // 引入之后调用
      console.log(time.format("11111111"));
      console.log(price.format(123));
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

    (四)类型的查找与声明

    项目中的类型,几乎都是我们自己编写的,但是也有一些其它的类型:

    const imageEl = document.getElementById("image") as HTMLAnchorElement;
    
    • 1

    都会很好奇,这里的HTMLAnchorElement的类型来自哪里?
    这里涉及到typescript对类型的管理查找规则:
    有关typescript的文件: .d.ts文件;用于做类型的声明(declare),仅仅用来做类型的检测,告知typescript我们拥有哪些类型;
    typescript会在哪里查找我们的类型声明呢?

    • 内置类型声明
    • 外部定义类型声明
    • 自己定义类型声明

    1.内置类型声明

    内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件; 包括MathDate等内置类型,也包括DOM API(Window、Document);

    const imageEl = document.getElementById("image") as HTMLAnchorElement;
    
    • 1

    如这里的getElementById的属性,在项目的配置文件中是可以查找到;
    在这里插入图片描述
    内置类型声明通常在我们安装typescript的环境中会带有的;

    点击项目文件查看lib相关的.d.ts文件:https://github.com/microsoft/TypeScript/tree/main/lib

    2.外部定义类型声明

    外部类型声明通常是我们使用一些库(比如导入第三方库),需要额外的添加类型的声明去使用;
    这些库通常有俩种类型声明的方式:
    方式一:在自己的库中自带的类型声明;导入的第三方库在node_modules文件中有自己的(.d.ts文件/或可添加);比如axios;
    方式二:通过社区公有库DefinitelyTyped存放类型声明文件
    1. 该库的GitHub地址:https://github.com/DefinitelyTyped/DefinitelyTyped/;社区中有大量的已编译好的.d.ts文件可供使用
    2. 该库查找声明安装方式的地址:https://www.typescriptlang.org/dt/search?search= ;该地址用于查询项目中所引用的第三方包导入的dt文件,可查看额外的指令去导入,省去翻阅安装包的目的; 比如我们输入react;安装react的类型声明: npm i @types/react --save-dev
    在这里插入图片描述

    3.自定义声明

    当在第三方库中没有声明的文件,并且我们想给自己的代码声明一些类型时,就可自定义声明文件;那么怎么自定义声明文件呢?项目的根目录下创建一个任意文件名.d.ts文件,并进行编译需要声明的类型:

    声明变量/函数/类

    declare let whyName: string
    declare let whyAge: number
    declare let whyHeight: number
    declare function whyFoo(): void
    declare class Person {
      name: string
      age: number
      constructor(name: string, age: number)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们也可以声明模块,比如lodash模块默认不能使用的情况,可以自己来声明这个模块:

    声明模块

    声明模块的语法: declare module '模块名' {}
    在声明模块的内部,我们可以通过 export 导出对应库的函数等;

    // 声明模块 - lodash是模块的名称
    declare module 'lodash' {
      export function join(arr: any[]): void
    }
    
    • 1
    • 2
    • 3
    • 4

    声明文件:

    在开发vue的过程中,默认是不识别我们的.vue文件的,那么我们就需要对其进行文件的声明;
    在开发中我们使用了 jpg 这类图片文件,默认typescript也是不支持的,也需要对其进行声明;

    // .vue文件的声明
    declare module '*.vue' {
        import { DefineComponent } from 'vue';
        const Component: DefineComponent<{}, {}, any>;
        export default Component;
    } 
    // 声明文件
    declare module '*.jpg'
    declare module '*.jpeg'
    declare module '*.png'
    declare module '*.svg'
    declare module '*.gif'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    声明命名空间

    比如我们在index.html中直接引入了jQuery
    CDN地址: https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js

    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js
      "></script>
    
    • 1
    • 2

    如果在.ts文件中直接使用就会导致运行报错:

    TS2581: Cannot find name ‘$’. Do you need to install type definitions> for jQuery? Try npm i --save-dev @types/jquery.

    而解决方案有俩种:

    • 方式一:安装@types/jquerynpm i --save-dev @types/jquery
    • 方式二:是添加$的命名空间:
    // 声明命名空间
    declare namespace $ {
      export function ajax(settings: any): any
    }
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    Microsoft Visual Studio C++开发环境的配置及使用
    【信息】宁波银行金融科技部:常见问题解答
    YOLOv8-Seg改进:轻量级Backbone改进 | VanillaNet极简神经网络模型 | 华为诺亚2023
    python中sort与sorted使用方法
    webpack使用eslint
    【Hack The Box】linux练习-- Doctor
    安全领航,共筑敏捷开发新时代【云驻共创】
    Android学习笔记 76. 支持库
    Seata之AT模式原理详解(三)
    Tcp 协议的接口测试
  • 原文地址:https://blog.csdn.net/weixin_42369598/article/details/125550283