TypeScript接口与泛型的使用

(一)TypeScript接口

1.声明一个对象类型

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

接口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

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
}

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)

4.接口的继承

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

interface ISwim {
  swimming: () => void
}

interface IFly {
  flying: () => void
}
interface IAction extends ISwim, IFly {
}
const action: IAction = {
  swimming() {
  },
  flying() {
  }
}

5.交叉类型

继联合类型的使用:

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

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

type WType = number & string;

但不存在一个变量即满足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() {
  }
}

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() {}})

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: "广州市",
})

类型检测不通过
请添加图片描述
而这里只使用自己定义好的值,多余的值会进行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的值

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)

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

enum Direction {
  LEFT = "LEFT",
  RIGHT = "RIGHT",
  TOP = "TOP",
  BOTTOM = "BOTTOM"
}

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

enum Direction {
  LEFT = 100,
  RIGHT,
  TOP,
  BOTTOM
}

(二)TypeScript泛型的使用

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

1.泛型的基本使用

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

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

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

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

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

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

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

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

2.泛型可传入多个参数

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

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

  • 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)
  }
}

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")

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})

(三)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;
    }
    
  • 命名空间:通过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));
    

(四)类型的查找与声明

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

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

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

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

1.内置类型声明

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

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

如这里的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)
}

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

声明模块

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

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

声明文件:

在开发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'

声明命名空间

比如我们在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>

如果在.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
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值