TypeScript 类、泛型的使用实践记录 | 青训营

69 阅读8分钟

TypeScript 类、泛型的使用实践记录

这是我参与「第六届青训营 」伴学笔记创作活动的第 7 天

引言

TypeScript 是 JavaScript 的超集,它扩展了 JavaScript 的功能,使其更适合大型项目的开发。在 TypeScript 中,类和泛型是两个重要的概念。类可以帮助我们组织代码,并将数据和相关的行为封装在一起;而泛型则可以使我们的代码更通用和灵活。本文将通过实例和解释,详细展示 TypeScript 中类和泛型的使用。

为什么要学习 TS

现在的趋势

image.png

TS 的活跃度日益增高,在工程里的应用比较广泛。

TS 和 JS 的对比

image.png

TS 由微软团队开发,是 JS 的一个超集,在 JS 的基础上去做一些类型检测和相关的语法补充,本质上为 JS 增加了静态类型和一些面向对象编程的相关能力(或者说是后端语言的相关特性)。

TS 带来了什么

image.png

  1. 类型安全,类似 Java、C++
  2. 包含了一些编译器,支持 JS 里面 ES 的新特性,功能类似于Bable 之类的处理
  3. 因为 TS 带来了类型,我们可以从 IDE 上更容易去理解代码,可以围绕着这些编码属性来整理一些相关的工具,来提高我们的开发效率

所以说,TS 不是一门语言,而是一种开发工具

Typescript 学习推荐

Awesome Typescript:Github 上整理的的 TS 开源教程及应用

ByteTech:TS&React:React + Typescript 开发模式的介绍

TypeScript Playground:TS到JS在线编译

类 - class

Typescript 类的介绍和定义

在 TypeScript 中,类是一种用于创建对象的蓝图或模板。它定义了对象的属性和方法,以及其行为和状态的集合。类在面向对象编程中起到了重要的作用,它具有以下特点:

  1. 封装性:类可以将属性和方法封装在一起,以形成一个对象,并隐藏内部实现细节。这样可以防止外部对对象的直接访问和篡改,增加了数据的安全性。
  2. 继承性:类可以通过继承关系扩展现有的类,并可以重用父类的属性和方法。子类可以添加新的属性和方法,以满足特定的需求。
  3. 多态性:类可以通过继承和接口来实现多态。多态允许子类以自己特定的方式重写父类的方法或属性,从而实现不同的行为。

TS 支持 ES6 引入的类的关键字,在此基础上补充了相关的语法糖,提高了效率和阅读性。TS 和 JS 的类的定义有一些不同。

定义:写法和JS差不多,增加了一些定义

在 TypeScript 中,我们可以通过关键字 class 来定义一个类。以下是一个简单的示例:

class Person {
  name: string;              // 属性
  constructor(name: string) {  // 构造函数
    this.name = name;
  }
  sayHello() {                // 方法
    console.log(`Hello, my name is ${this.name}.`);
  }
}

let person = new Person("Alice");  // 创建一个 Person 对象
person.sayHello();                 // 调用对象的方法

在上面的代码中,我们定义了一个名为 Person 的类。它有一个属性 name 和一个构造函数 constructor,构造函数用于初始化类的实例。Person 类还有一个方法 sayHello(),用于打印一个问候语。我们可以通过 new 关键字实例化一个 Person 对象,并调用其方法。

通过类的定义,我们可以创建具有共享属性和行为的对象,并将其用于代码的组织和复用。

特点:超级

  • 增加了 public、private、protected 修饰符

看下这个 Demo:

image.png

首先,默认的方法和属性都是默认 public 的

然后,private 是私有属性,private 定义之后,继承类和实例都不能去调用这个私有属性

最后,protected 是受保护的,仅支持再继承类中被调用,生成的一些实例是不能调用的

  • 抽象类:
    • 只能被继承,不能被实例化
    • 作为基类,抽象方法必须被子类实现

抽象类的相关 Demo:

image.png

我们无法对 Animal 进行实例化,只能对它进行继承,然后对他的子类进行实例化的操作过程。

要注意的一点是,在 TS 中,井号标识是表示这是一个私有属性,外界是无法进行调用的。

  • interface 约束类,使用 implements 关键字

如果要使用 interface 来约束类的话,可以使用 implements 关键字,来进行接口方面的定义,方便开发者来判断当前的类是否满足接口相关的定义。

TypeScript类的简单使用实践

在TypeScript中,类是一种封装了属性和方法的数据结构。它可以帮助我们组织和管理代码,并提供了面向对象编程的特性。下面是一个使用TypeScript类的示例:

class Person {
  private name: string;
  private age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  public introduce(): void {
    console.log(`My name is ${this.name} and I'm ${this.age} years old.`);
  }
}

const person = new Person("John", 25);
person.introduce(); // 输出:My name is John and I'm 25 years old.

在上面的示例中,定义了一个名为Person的类。它有两个私有属性nameage,并通过构造函数接收这两个属性的值。类还有一个公共方法introduce,用于打印自我介绍的信息。在类的实例化过程中,传入了名为John和25的参数,并调用了introduce方法,打印了John的自我介绍。

这个示例展示了TypeScript类的基本结构和使用方法。可以继续通过添加更多属性和方法来扩展这个类,从而实现更复杂的功能。

泛型

在 Typescript 高级类型中,分为需要泛型和不需要泛型的时候,下面我们来讲讲涉及到泛型和什么时候需要泛型

泛型 - 什么时候需要泛型

官方定义:

软件工程中,我们不仅要创建一致的的定义良好的 API,同时也要考虑到可重用性。

组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件。

一个组件可以支持多种类型的数据,这样用户就可以以自己的数据类型来使用组件。

TypeScript 泛型介绍

TypeScript 的泛型是一种在编程中用于增强代码灵活性和重用性的特性。通过使用泛型,可以在定义函数、类或接口时不指定具体类型,而是使用占位符(类型参数)。在使用时,可以根据实际情况传入不同的具体类型,从而实现对不同类型的支持和复用。

泛型的优点有以下几个方面:

  1. 代码重用:使用泛型可以编写通用的代码,可以轻松地适应多种类型的数据,减少了重复代码的编写。
  2. 类型安全:通过使用泛型,可以在编译时捕获类型错误,并提供更好的类型检查和推断。
  3. 抽象数据类型的支持:泛型可以用于创建抽象数据类型,例如集合类(List、Stack)或容器类,使其更加通用。

下面是一些常见的使用泛型的情况:

  1. 泛型函数:定义一个函数时,可以使用泛型来处理输入参数或返回值的类型。例如,function identity<T>(arg: T): T { ... },这个函数可以接受任意类型的参数,并返回相同类型的值。
  2. 泛型类:类可以使用泛型来定义属性、方法或构造函数的参数。例如,class ArrayList<T> { ... },这个类可以创建一个可操作各种类型的数组。
  3. 泛型接口:接口也可以使用泛型来定义属性、方法或函数的类型。例如,interface List<T> { ... },这个接口可以定义一个通用的列表结构。

需要注意的是,TypeScript 的泛型只在编译时起作用,运行时会被擦除,因为 JavaScript 是一种动态类型的语言。因此,泛型在编译阶段会进行类型检查,但在真正执行时,并不会对泛型进行运行时类型检查。

  • 应用场景:定义一个print函数,这个函数的功能是把传入的参数打印出来,再返回这个参数,传入参数的类型是string,函数返回类型是string.1、想支持打印number类型?2、想支持打印数据类型?任意类型? 思考:需要有一个类型解决输入输出可关联的问题
function print(arg:string):string{
    console.log(arg)
    return arg
}
function print(arg:string | number):string | number{
    console.log(arg)
    return arg
}
function print(arg:any):any{
    console.log(arg)
    return arg
}
function print<T>(arg:T):T{
    console.log(arg)
    return arg
}
  • 泛型的语法是<>里面写类型参数,一般用T表示;
  • 使用时有两种方法指定类型:1、定义要使用的类型;2、通过TS类型推断,自动推导类型
  • (泛型的核心)泛型的作用是临时占位,之后通过传来的类型进行推导
function print<T>(arg:T):T{
    console.log(arg)
    return arg
}
print<string>('hello') // 定义T为string
print('hello') // TS类型推断,自动推导类型为string
  • 泛型工具类型-基础操作符:
    • typeof:获取类型
    • keyof:获取所有键
    • in:遍历枚举类型
    • T:索引访问
    • extends:泛型约束 image.png

image.png

TypeScript泛型的简单使用实践

在TypeScript中,泛型允许在定义函数、类或接口时延迟指定具体类型,以实现代码的复用性和灵活性。下面是一个使用TypeScript泛型的示例:

function reverse<T>(items: T[]): T[] {
  return items.reverse();
}

const numbers = [1, 2, 3, 4, 5];
const reversedNumbers = reverse(numbers);
console.log(reversedNumbers); // 输出:[5, 4, 3, 2, 1]

const strings = ["hello", "world"];
const reversedStrings = reverse(strings);
console.log(reversedStrings); // 输出:["world", "hello"]

在上面的示例中,定义了一个名为reverse的函数。它接收一个类型为T的数组作为参数,并返回一个反转后的数组。通过使用泛型类型参数T,可以将函数应用于不同类型的数组,从而实现了代码的复用性。

这个示例展示了TypeScript泛型的灵活性和复用性。可以在函数、类和接口中使用泛型,以适应不同类型的数据。

TS工程化实例

TS实战

  • declare:三方库需要类型声明文件
  • .d.ts:声明文件定义
  • @types:三方库TS类型包
  • tsconfig.json:定义TS的配置

案例

参考:juejin.cn/post/706290…

以下代码为什么会提示错误,应该如何解决?

type User = {
    id:number;
    kind:string;
}
function makeCustomer<T extends User>(u: T): T{
    // Error
    // Type '{id: number; kind: string}' is not assignable to type 'T'
    // '{id: number; kind: string;}' is assignable to the constraint of type 'T'
    // but 'T' could be instaniated with a different subtype of constraint 'User'
    return {
        id: u.id;
        kind: 'customer
    }
}

出错原因:泛型T只是约束于User类型,并不是局限于User类型,所以返回结果应该还需要接收其他类型变量。

  • 解决办法一:T类型兼容User类型
function makeCustomer<T extends User>(u: T): T{
    return {
    ...u,
    id: u.id,
    kind:'customer'
}
  • 解决方法二:返回值类型修改为User类型
function makeCustomer<T extends User>(u: T): User{
    return {
        id: u.id,
        kind: 'customer',
    }
}
function makeCustomer<T extends User>(u: T): ReturnMake<T, User>{
    return {
        id: u.id,
        kind: 'customer',
    }
}
type ReturnMake<T extends User, U> = {
    [k in keyof U as K extends keyof T ? K: never]: U[K]
}

makeCustomer({id:18923323, kind: '888', price: 99})
// 1、ReturnMake工具类型,接收T、U两个泛型,T约束于User
// 2、遍历User中的Key,并使用as断言,如果K(也就是User类型的key),约束于泛型类型的key返回k,否则返回never,U[K]取键值