TypeScript——泛型理论与实践

news2025/5/24 12:58:09

1. 简介

软件工程的一个重要部分就是构建组件,组件不仅需要有定义良好和一致的
API,还需要是可复用的。好的组件不仅能够兼容现有的数据类型,也能适用于未来可能出现的数据类型,这在构建大型软件系统时会有很大的灵活度以及很高的扩展性。
在比如 C# 和 Java
语言中,用来创建可复用组件的工具,我们称之为泛型。利用泛型,我们可以创建一个支持众多类型的组件,并且用户在使用组件时可以传入自己想要的类型。

2. 举个🌰

上述的简介还是不够通俗,我们接下来结合一些简单代码来理解什么是泛型:

function getVal(val: string): string {
    return val
}

const foo = getVal('123')

const bar = getVal(123)

在这里插入图片描述

我们定义了一个函数 getVal,指定接收一个类型为 string 的参数,并将入参作为返回值。
结合代码图示,我们老老实实传入 string 类型的值后,可以看到代码正常,并且 TypeScript 很智能的给出编码提示。在我们传入 number 类型的值时也会很及时的给出报错信息,这很好、很强大!
但是,如果我们编写的方法就是需要接收多种类型的参数,那该如何呢?🤔
小明同学不假思索的写出以下代码:

function getVal(val: string | number): string | number {
    return val
}

const foo = getVal(123)

通过联合类型(Union Types)实现了 getVal 函数接收多种类型参数的需求,并且运行完美没有任何报错。只是由于返回值也是一个联合类型,所以 TypeScript 并不能准确知道函数具体的返回值类型。因此导致 TypeScript 给出的代码提示是两种类型值的共有方法:

在这里插入图片描述

我们想要实现的函数功能是:接收任意类型的入参,返回值等同于入参的类型,这很明显不符合我们的预期。

3. 泛型——函数

我们可以使用泛型(Generics)来实现上面的函数功能:

function getVal<T>(val: T): T {
    return val
}

const foo = getVal(123)

const bar = getVal('123')

上面示例中,函数 getVal() 的函数名后面尖括号的部分 <T>,就是泛型的类型参数,参数要放在一对尖括号 <> 里面。
泛型的类型参数本质就是一个接收 TypeScript类型,它和 js 中函数定义的参数(形参)极为类似,只不过在 js 中形参接收的是值,在 TypeScript 中类型参数接收的是类型。

在这里插入图片描述

在这里插入图片描述

通过使用类型参数接收指定类型,我们精准的得到函数返回值类型,并享受更好的智能提示。
💡大多数时候我们不必显式的传入类型参数,因为 TypeScript 可以根据传入的值推断出它的类型,所以更推荐下面的写法:

function getVal<T>(val: T): T {
    return val
}

const foo = getVal(123)

在这里插入图片描述

4. 泛型——接口

通过上面的内容,我们已经感受到泛型很方便、灵活,然而它的强大之处才体现出冰山一角。

  1. 使用 interface 描述一个对象时,动态定义某个属性的类型
interface Data<T> {
  name: string;
  val: T;
}

const foo: Data<number> = {
  name: "tom",
  val: 123,
};

const bar: Data<string> = {
  name: "tom",
  val: "123",
};
  1. 使用 interface 定义一个泛型函数的类型
function id<Type>(arg: Type): Type {
  return arg;
}

let myId

myId = id

在这里插入图片描述

普通函数的类型好定义 type Fn = (arg: any) => void ,泛型函数的类型如何定义呢?
其实编辑器也是可以给出答案的:

在这里插入图片描述

粘一下代码即可:

interface Fn {
    <Type>(arg: Type): Type
}

function id<Type>(arg: Type): Type {
  return arg;
}

let myId: Fn

myId = id // ✔️

myId = 123 // ❌

在这里插入图片描述

5. 泛型——类

泛型类的类型参数写在类名后面:

class Person<Name, Age> {
  name: Name;
  age: Age;

  constructor(name: Name, age: Age) {
    this.name = name;
    this.age = age;
  }
}

new Person<string, number>("Bob", 88);

下面是继承泛型类的例子:

class Person<Name, Age> {
  name: Name;
  age: Age;

  constructor(name: Name, age: Age) {
    this.name = name;
    this.age = age;
  }
}

class Teacher extends Person<string, number> {
    gender: string = 'male'
}

上面示例中, Person 类有两个类型参数,分别是 NameAge,使用时必须给出 NameAge 的类型,所以 Teacher 类继承时按照需要把类型参数写进去就行了 Person<string, number>
但是如果我们想在初始化 Teacher 类的时候动态传入 Person 类的类型参数,那该如何呢?🤔🤔
写法应该是一样的,我们可以尝试写出以下代码:

class Person<Name, Age> {
  name: Name;
  age: Age;

  constructor(name: Name, age: Age) {
    this.name = name;
    this.age = age;
  }
}

class Teacher<Name, Age, Gender> extends Person<Name, Age> {
  gender: Gender;

  constructor(name: Name, age: Age, gender: Gender) {
    super(name, age);
    this.gender = gender;
  }
}

type Gender = "male" | "woman";

const teacher = new Teacher<string, number, Gender>("Bob", 88, "male");

我们可以看到,类型正常运作,结果也正常:

在这里插入图片描述

6. 泛型——类型别名

相较于接口、类中的泛型运用,在类型别名中泛型的使用可能更常见一点:

type CustomUnion<T> = T | string

以上是泛型的简单应用,通过动态的传入类型,便拥有了一个自定义的联合类型。
下面是定义树形结构的例子:

type Tree<T> = {
  value: T;
  children: Tree<T>[];
};

const tree: Tree<string> = {
  value: "tree-1",
  children: [
    {
      value: "tree-1-1",
      children: [],
    },
  ],
};

在类型别名内部递归引用自身时,传入泛型,依然能够很好的工作:

在这里插入图片描述

7. 泛型——类型参数默认值

我们在上面讲到 “类型参数极其类似 js 中函数的形参”,在 ES6 中函数的形参可以定义默认值,这很优雅,非常方便。在泛型中定义默认类型同样非常类似:

// 定义类型参数默认类型
function getVal<T = string>(val: T): T {
    return val
}

上面示例中,T = string 表示类型参数的默认值是 string。调用 getVal() 时,如果不给出 T 的值,TypeScript 就认为 T 等于 string
但是,因为 TypeScript 会从实际参数推断出 T 的值,从而覆盖掉默认值,所以下面的代码不会报错。

getVal(true) //✔️✔️

在这里插入图片描述

8. 泛型——类型约束

很多时候类型参数并不是无限制的,我们也可以对于传入的类型制定约束条件。

type ButtonType = 'default' | 'primary'

function getButtonType<T>(type: T): T {
    return type
}

getButtonType('123')

就像这个🌰,我们传入 '123' 后运行很稳定、不报错,但是很明显 getButtonType 的参数不能随便传。
根据代码块内容,它只能接收 'default''primary' 这两个参数,我们如何做到呢?🤔🤔🤔
这种情况下真乃是泛型约束的绝佳应用场景!泛型约束的语法尤为简单,像 T extends string 这样。
在 TypeScript中,T extends string 是一个类型限界表达式,它表示类型参数 T 必须是一个字符串类型或字符串类型的子类型。我们来轻微改写一下:

type ButtonType = 'default' | 'primary'

function getButtonType<T extends ButtonType>(type: T): T {
    return type
}

这样改过之后,不仅可以准确的给出报错提示,

在这里插入图片描述

还能给我们提供便捷的智能提示,这对于团队协作的可靠性及高效率也有着很大的提升!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1010685.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android嵌套事务

这时候旋转设备还是会重置秒表。旋转设备时Android会重新创建活动。如果你的活动包含一个 < fragment >元素&#xff0c;每次重新创建活动时&#xff0c;它会重新插入片段的一个新版本。老片段被丢掉&#xff0c;所有实例变量会设置其初始值。在这个特定的例子中&#xf…

基于微信小程序的“共享书角”图书借还管理系统(springboot+vue)

为设计一个安全便捷&#xff0c;并且使借阅者更好获取本图书借还信息&#xff0c;本文主要有安全、简洁为理念&#xff0c;实现借阅者快捷寻找图书借还信息&#xff0c;从而解决图书借还信息复杂难辨的问题。该系统以springboot架构技术为基础&#xff0c;采用Java语言和MySQL数…

家政服务预约小程序,推拿spa上门预约系统

家政服务预约小程序&#xff0c;用户直接发布需求下单&#xff0c;师傅入驻抢单派单&#xff0c;多商家入驻&#xff0c;上门预约服务流程清晰&#xff0c;适合家政公司或需要预约场景的团队公司使用&#xff0c;支持多种行业上门预约服务场景&#xff1a;家政保洁维修上门服务…

CISP认证介绍(CISECISO)

0x00 前言 CTF 加解密合集CTF Web合集网络安全知识库溯源相关 文中工具皆可关注 皓月当空w 公众号 发送关键字 工具 获取 0x01 什么是CISP CISP &#xff08;Certified Information Security Professional&#xff09; 注册信息安全专业人员资格认证&#xff0c;由中国信息…

几个好用的数据标注软件labelme、CVAT及LabelImage

我们使用yolov3、yolov4、yolov5、yolov8等训练自己的权重时&#xff0c;需要有大量标注好的数据集&#xff0c;这里有几个好用的数据标注软件labelme、CVAT及LabelImage 一、labelme labelme&#xff1a;https://github.com/wkentaro/labelme 这个软件用的比较多&#xff0c…

介绍Spring MVC框架,以及如何使用它构建Web应用程序。

文章目录 什么是 Spring MVC&#xff1f;Spring MVC 的工作原理如何使用 Spring MVC 构建 Web 应用程序配置 Spring MVC创建控制器创建视图配置 Spring MVC 配置文件运行应用程序 总结 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;欢…

baichuan-53B VS ChatGLM-6B对比

由于百川智能的内测模型是baichuan-53B&#xff0c;尽管模型大小不一致&#xff0c;为了方便&#xff0c;我们仍然选择百川智能baichuan-53B与ChatGLM-6B内测结果进行对比&#xff0c;其中ChatGLM-6B的结果来自https://github.com/THUDM/ChatGLM-6B&#xff0c;假设ChatGLM-6B的…

Spring (2) AOP

目录 1 代理(Proxy)模式 1.1 静态代理 1.2 动态代理 1.2.1 基于接口的动态代理 1.2.2 基于子类的动态代理 2 AOP 2.1 注解开发 1 代理(Proxy)模式 一种设计模式,它的作用是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接调用,而是通过代理类间接调用 1.1 静…

可降阶的高阶方程与高阶线性微分方程

目录 可降阶的高阶方程 高阶线性微分方程 齐次方程 非齐次方程 常系数齐次线性微分方程 常系数非齐次线性微分方程 可降阶的高阶方程 我们需要先理解什么是可降解的高阶微分方程。可降解的高阶微分方程是指可以转化为低阶微分方程的方程。 例如&#xff0c;以下是一个二阶…

docker 获取Nvidia 镜像 | cuda |cudnn

本文分享如何使用docker获取Nvidia 镜像&#xff0c;包括cuda10、cuda11等不同版本&#xff0c;cudnn7、cudnn8等&#xff0c;快速搭建深度学习环境。 1、来到docker hub官网&#xff0c;查看有那些Nvidia 镜像 https://hub.docker.com/r/nvidia/cuda/tags?page2&name11.…

高德地图实现-逆地理编码-输入提示-地图标点-实现车库管理

效果图&#xff1a; 我们将学习如何创建一个前端地图应用程序&#xff0c;该应用程序集成了高德地图API&#xff0c;允许用户进行地点搜索、选择和标记&#xff0c;以及执行逆地理编码以获取地址信息。我们将使用Vue.js框架来构建应用程序&#xff0c;并结合高德地图的功能来实…

.NET Upgrade Assistant 升级 .NET MAUI

.NET Upgrade Assistant 是一种可帮助您将应用程序升级到最新的 .NET版本 的工具&#xff0c;并且您可以使用这个工具将您的应用程序从旧平台&#xff08;例如 Xamarin Forms 和 UWP&#xff09;迁移到新的平台。此外&#xff0c;这个新版本的工具&#xff0c;可以让您在不更改…

C++ - map 和 set 使用介绍

简介 其实在map 和 set 的底层实现当中有差不多一半的 结构是使用 二叉搜索树来实现&#xff0c;关于二叉搜索树可以看下面这个篇博客&#xff1a;C - 搜索二叉树_chihiro1122的博客-CSDN博客 而 set 是 key 模型&#xff0c;他是直接按照 key 值大小来有规律的在 二叉搜索树当…

pyG教程

introduction中ShapeNet失效问题解决 首先用github中官方最新的shapenet.py中文提示手动下载数据压缩包 # In case shapenet.cs.stanford.edu is offline, try to download the data# from Kaggle instead (requires login):# https://www.kaggle.com/datasets/mitkir/shapene…

提升网速 网卡和驱动

去某宝下单了&#xff0c;是网卡和驱动的问题&#xff08;某宝说网卡和驱动是一个意思&#xff09;。以下是操作步骤。 更新驱动程序后从联想官网下载本台电脑对应的驱动 下载可以识别自己主机编号的软件-主机信息识别工具 然后获取编号 然后下载自己的有线网卡

探索装饰艺术的未来,留存传统的精髓

近一个世纪后&#xff0c;装饰艺术终于卷土重来。正如我们在全球新的项目、室内空间和家具中所看到的&#xff0c;那种令我们渴望20世纪初20年代繁荣时期的奢华和魅力。作为装饰艺术建筑和设计的独特身份一直在世界上继续启发着人们&#xff0c;那么从新的设计和现有设计的保留…

5.linux的定时任务调度crontab

一、定时任务调度crontab 1. 定时任务调度crond介绍 ①任务调度&#xff1a;系统在某个时间执行特定的命令和程序 ②任务调度分类&#xff1a;系统工作&#xff08;病毒扫描&#xff09;&#xff0c;用户工作&#xff08;备份mysql数据库&#xff09; 2.定时任务调度crond原…

C++项目实战——基于多设计模式下的同步异步日志系统-⑤-实用工具类设计

文章目录 专栏导读获取系统时间time介绍 getTime函数设计判断文件是否存在stat介绍exists函数设计 获取文件所在路径find_last_of介绍path函数设计 创建文件所在目录mkdir介绍find_first_of介绍函数createDirectory设计 实用工具类整理 专栏导读 &#x1f338;作者简介&#xf…

数据的真正价值是数据要素市场化开发

随着人工智能、互联网、物联网、大数据、云计算、区块链等新一代信息化、数字化技术的应用&#xff0c;各行各业都开始了新一轮的产业革命和转型升级。在这个过程中&#xff0c;数据伴随着信息化、数字化的推进越发变得重要&#xff0c;到了2020年直接成为了继土地、劳动力、资…

人工智能AI 全栈体系(三)

第一章 神经网络是如何实现的 一个神经网络用不同的数据做训练&#xff0c;就可以识别不同的东西&#xff0c;那么神经网络究竟是怎么训练的&#xff1f; 三、神经网络是如何训练的&#xff1f; 1. 小朋友如何认识小动物&#xff1f; 小时候&#xff0c;每当看到一个小动物时…