TypeScript高级类型技巧:泛型、联合与交叉类型

泛型

在TypeScript中,泛型是一种强大的工具,它允许我们编写可重用的组件,这些组件可以适应多种类型。

1. 泛型约束(Generic Constraints)

泛型可以被约束在一个特定的类型或类型接口上,确保传递给泛型的类型满足一定的条件。例如,如果我们希望一个函数只接收具有 length 属性的类型,我们可以这样做:

interface Lengthwise {length: number;
}function printLength<T extends Lengthwise>(item: T): void {console.log(item.length);
}const stringLength = "hello";
printLength(stringLength); // OK, strings have a length property
const objectWithoutLength = { name: "World" };
printLength(objectWithoutLength); // Error, no length property

<T extends Lengthwise> 确保 T 必须具有 length 属性。

2. 类型推断(Type Inference with Generics)

TypeScript允许在某些情况下自动推断泛型的类型,特别是在函数调用时。例如,使用 infer

关键字可以从函数类型中提取返回值类型:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;function identity<T>(arg: T): T {return arg;
}type IdentityReturnType = ReturnType<typeof identity>; // IdentityReturnType is inferred as 'string'

在这里,ReturnType 会从函数类型中提取返回值类型。

3. 多参数泛型(Multi-Parameter Generics)

可以定义接受多个泛型参数的类型或函数:

interface Pair<T, U> {first: T;second: U;
}function createPair<T, U>(first: T, second: U): Pair<T, U> {return { first, second };
}const pair = createPair("Hello", 42); // pair has type Pair<string, number>

createPair 函数接受两个泛型参数 TU,并返回一个 Pair<T, U> 类型的对象。

4. 泛型接口(Generic Interfaces)

接口也可以使用泛型:

interface GenericIdentityFn<T> {(arg: T): T;
}function identity<T>(arg: T): T {return arg;
}let myIdentity: GenericIdentityFn<number> = identity; // myIdentity is a number-specific version of the identity function

这里,GenericIdentityFn 是一个泛型接口,identity 函数被赋值给它的一个实例,限制了它只能处理 number 类型的参数。

5. 泛型类(Generic Classes)

类也可以定义为泛型,允许类的方法和属性使用不同的类型:

class Box<T> {value: T;constructor(value: T) {this.value = value;}setValue(newValue: T): void {this.value = newValue;}
}const boxOfStrings = new Box<string>("Hello");
boxOfStrings.setValue("World"); // OK
boxOfStrings.setValue(123); // Error, incompatible types

Box 类接受一个泛型类型 T,并在实例化时指定具体类型。

联合

TypeScript 中的联合类型(Union Types)允许我们将多种类型合并成一个新的类型,这意味着一个变量可以是多种类型中的一种。

1. 类型保护与类型断言(Type Guards)

当你处理联合类型时,有时需要确定变量的具体类型。这可以通过类型保护来实现,即编写一个函数或表达式来检查变量的性质,从而缩小其可能的类型范围。例如:

type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number };function getArea(shape: Shape): number {if ('radius' in shape) {// 类型保护:现在我们知道 shape 是 { kind: 'circle'; radius: number }return Math.PI * shape.radius ** 2;} else {// 类型保护:现在我们知道 shape 是 { kind: 'square'; side: number }return shape.side ** 2;}
}

在这个例子中,通过检查 shape 是否有 radius 属性,我们能确定它是一个圆还是一个正方形。

2. 非空断言操作符(Non-null assertion operator, !)

非空断言操作符 ! 可以用来告诉编译器,即使联合类型中可能包含 nullundefined,你确定这个值不会是 null 或 undefined。但是,如果判断错误,运行时可能会抛出错误:

function logValue(value: string | null | undefined): void {if (value) {console.log(value!.toUpperCase()); // 使用 ! 进行非空断言} else {console.log('Value is null or undefined');}
}

在这里,如果 value 不是 nullundefined,我们使用 ! 来忽略潜在的 nullundefined 类型警告。

3. 类型拆解(Distributive Type Operator, & 和 |)

当你对一个泛型类型应用联合或交叉类型操作时,它们会“拆解”到泛型的每一个实例上。例如,对于一个数组的元素类型是联合类型的情况:

type NumbersOrStrings = number | string;
type ArrayWithMixedElements<T> = T[];const mixedArray: ArrayWithMixedElements<NumbersOrStrings> = [1, "two", 3];

mixedArray 的元素类型是 NumbersOrStrings 的每一个实例,所以它可以包含 numberstring

4. 模式匹配(Pattern Matching)

在解构赋值、函数参数或类型别名中,可以使用模式匹配来处理联合类型的值:

type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number };function handleShape(shape: Shape) {switch (shape.kind) {case 'circle':const { radius } = shape;// 现在我们知道了 shape 是 { kind: 'circle'; radius: number }break;case 'square':const { side } = shape;// 现在我们知道了 shape 是 { kind: 'square'; side: number }break;}
}

在这个例子中,switch 语句作为类型保护,根据 kind 属性来处理不同类型的形状。

交叉类型

交叉类型(Intersection Types)在 TypeScript 中允许你将多个类型合并成一个新类型,这个新类型包含了所有原始类型的属性和方法。

1. 类型合并(Combining Types)

交叉类型使用 & 运算符来合并两个或更多类型。例如,假设我们有两个接口 PersonEmployee,我们可以创建一个 PersonAndEmployee 交叉类型:

interface Person {name: string;age: number;
}interface Employee {id: number;department: string;
}type PersonAndEmployee = Person & Employee;const person: PersonAndEmployee = {name: 'Alice',age: 30,id: 123,department: 'HR',
};

person 变量现在必须同时符合 PersonEmployee 接口的要求。

2. 类与接口的交叉

交叉类型也可以应用于类与接口之间,将类的实例与接口的属性和方法相结合:

class Animal {name: string;makeSound(): void {console.log('Making sound...');}
}interface HasColor {color: string;
}class ColoredAnimal extends Animal implements HasColor {color: string;
}type ColoredAnimalIntersection = Animal & HasColor;function describeAnimal(animal: ColoredAnimalIntersection) {console.log(`The ${animal.name} is ${animal.color} and makes a sound.`);animal.makeSound();
}const coloredCat = new ColoredAnimal();
coloredCat.name = 'Kitty';
coloredCat.color = 'Gray';
describeAnimal(coloredCat);

ColoredAnimalIntersection 类型既是 Animal 类的实例,也拥有 HasColor 接口的 color 属性。

3. 类型防护(Type Guard)

交叉类型在类型防护中也很有用,尤其是当你需要在联合类型中确定一个特定的类型。例如,你可能有一个对象,它可能是两种类型之一,但你希望在某个时刻确定它是哪一种:

typescript
interface Movable {move(): void;
}interface Static {stay(): void;
}type ObjectState = Movable & Static;function isMovable(obj: ObjectState): obj is Movable {return typeof obj.move === 'function';
}const object: ObjectState = {move: () => console.log('Moving...'),stay: () => console.log('Staying...')
};if (isMovable(object)) {object.move(); // 类型防护确保了 move 方法存在
} else {object.stay();
}

isMovable 函数是一个类型保护,它检查 move 方法是否存在,如果存在,则表明 object 实际上是 Movable 类型。

2500G计算机入门到高级架构师开发资料超级大礼包免费送!

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

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

相关文章

RabbitMQ怎么保证可靠性

RabbitMQ怎么保证可靠性 前言生产端问题解决方案代码验证 RabbitMQ问题消费端问题解决方案代码验证 总结 前言 RabbitMQ相信大家都非常熟悉了&#xff0c;今天咱们来聊聊怎么保证RabbitMQ的可靠性。 那什么时候会出现问题呢&#xff1f; 第一种是生产端出现的问题。我们向队…

第五十天 进入子序列问题 | 300.最长递增子序列 674.最长连续递增序列 718.最长重复子数组

题目&#xff1a;300.最长递增子序列 1.dp数组的定义&#xff1a; 以nums[i]为结尾的最长递增子序列的长度 为什么一定表示 “以nums[i]结尾的最长递增子序” &#xff0c;因为我们在 做 递增比较的时候&#xff0c;如果比较 nums[j] 和 nums[i] 的大小&#xff0c;那么两个递…

MySQL-事务日志

事务的隔离性由 锁机制 实现 事务的原子性、一致性、隔离性 由事务的 redo日志 和 undo 日志来保证 redo log 称为 重做日志&#xff0c;提供再写入操作&#xff0c;恢复提交事务修改的页操作&#xff0c;用来保证事务的持久性。undo log 称为 回滚日志&#xff0c;回滚行记录…

selenium自动化介绍

文章目录 一、selenium原理 安装二、selenium使用1.创建浏览器对象&#xff0c;访问网址2.消除警告提示3.不显示浏览器中受控制字样4.防检测5.设置延时5.1强制延时5.2隐式延时 6.设置浏览器窗口大小 三、案例实战&#xff1a;百度搜索四、iframe标签五、案例实战&#xff1a;Q…

第一周 数据结构与算法以及复杂度分析

数据结构与算法 算法定义 算法&#xff08;algorithm&#xff09;是在有限时间内解决特定问题的一组指令或操作步骤&#xff0c;它具有以下特性。 1.问题是明确的&#xff0c;包含清晰的输入和输出定义。 2.具有可行性&#xff0c;能够在有限步骤、时间和内存空间下完成。 3.…

【第五节】C++的多态性与虚函数

目录 前言 一、子类型 二、静态联编和动态联编 三、虚函数 四、纯虚函数和抽象类 五、虚析构函数 六、重载&#xff0c;重定义与重写的异同 前言 面向对象程序设计语言的三大核心特性是封装性、继承性和多态性。封装性奠定了基础&#xff0c;继承性是实现代码重用和扩展…

Linux内网中安装jdk1.8详细教程

本章教程,主要介绍如何在内网环境中配置JDK1.8环境变量 一、下载Linux版压缩包 下载地址:https://www.oracle.com/java/technologies/downloads/#java8 下载完成之后,通过XFTP等工具,将安装包上传到内网服务器 二、安装配置步骤 1、解压压缩包 tar -zxvf /usr/local/jdk-…

linux--自动备份文件

问题&#xff1a; 1&#xff0c;rm删除无法找回&#xff1b; 2&#xff0c;使用git的时候会出现各种可能导致文件丢失&#xff0c;无法找回的情况。 3&#xff0c;......。 设置自动备份文件和目录

使用Python, 用shp文件边界裁剪tif文件

在Python中, 用shp文件边界裁剪tif文件 from osgeo import gdal import osgdal.PushErrorHandler("CPLQuietErrorHandler")def subset_by_shp(shape_fn, raster_fn, raster_out):"""根据 shapefile 对栅格文件进行裁剪并输出结果:param shape_fn: sh…

根据PDF模版填充数据并生成新的PDF

准备模版 使用 福昕高级PDF编辑器 &#xff08;本人用的这个&#xff0c;其他的也行&#xff0c;能作模版就行&#xff09;打开PDF文件点击 表单 选项&#xff0c;点击 文本域在需要填充数据的位置设计文本域设置 名称、提示名称相当于 属性名&#xff0c;提示就是提示&#x…

基于SSM的“基于Apriori算法的网络书城”的设计与实现(源码+数据库+文档)

基于SSM的“基于Apriori算法的网络书城”的设计与实现&#xff08;源码数据库文档) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 网站功能展示图 首页 商品分类 热销 新品 我的订单 个…

二位偏序,P3660 [USACO17FEB] Why Did the Cow Cross the Road III G

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 P3660 [USACO17FEB] Why Did the Cow Cross the Road III G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 二、解题报告 1、思路分析 二维偏序问题 我们将坐标按照第一维排序 然后树状数组维护区间内的…

【深度学习】【STWave】时空图预测,车流量预测,Efficient Spectral Graph Attention Network

Spatio-Temporal meets Wavelet: Disentangled Traffic Flow Forecasting via Efficient Spectral Graph Attention Network 代码&#xff1a;https://github.com/LMissher/STWave 论文&#xff1a;https://arxiv.org/abs/2112.02740 帮助&#xff1a; https://docs.qq.com/s…

C++STL---vector模拟实现

通过上篇文章&#xff0c;我们知道vector的接口实际上和string是差不多的&#xff0c;但是他俩的内部结构却大不一样&#xff0c;vector内有三个成员变量&#xff1a;_start、_finish、_endofstorage: _start指向容器的头元素&#xff0c;_finish指向有效元素末尾的元素&#x…

Vue2 + Element UI 封装 Table 递归多层级列表头动态

1、在 components 中创建 HeaderTable 文件夹&#xff0c;在创建 ColumnItem.vue 和 index.vue。 如下&#xff1a; 2、index.vue 代码内容&#xff0c;如下&#xff1a; <template><div><el-table:data"dataTableData"style"width: 100%"…

OSM历史10年(2014-2024)全国数据下载(路网、建筑物、POI、水系、地表覆盖利用······)

点击下方全系列课程学习 点击学习—>ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放 零、前沿 这次向大家介绍一下OSM&#xff08;OpenStreetMap&#xff09;十年历史数据&#xff08;2014—2014&#xff09;的下载方法。当然我们也下载好分享给大家&#xff…

JAVA web期末复习总结

C/S结构与B/S结构区别&#xff1a; 在C/S结构中&#xff0c;客户端通常是一个独立的应用程序&#xff0c;需要在用户的计算机上安装和运行。而在BS结构中&#xff0c;客户端是一个Web浏览器&#xff0c;用户只需要通过浏览器打开网页&#xff0c;不需要安装额外的应用程序。 C…

python正则表达式中的分组功能

在Python的re模块中&#xff0c;group()方法是用于从一个匹配的对象&#xff08;例如&#xff0c;re.match或re.search返回的对象&#xff09;中提取匹配的字符串。 当你使用正则表达式进行匹配时&#xff0c;匹配对象会包含原始字符串中与模式匹配的部分。group()方法可以用来…

Reactive 踩坑

vue 响应式踩坑 let questionInfo reactive([ , ]) api.getQuestions( id ).then(function (response){// 这里用法有问题questionInfo response.data.data.questions;concole.log(questionInfo) })响应式数据本身是个函数&#xff0c;&#xff08;不然咋帮你动态变化页面…

k8s_设置dns

配置k8s dns 在 Kubernetes 集群中&#xff0c;CoreDNS 是默认的 DNS 服务器&#xff0c;它负责处理集群内所有的 DNS 请求。 kubectl edit cm coredns -n kube-system (此命令修改coredns 配置) kubectl describe cm coredns -n kube-system&#xff08;此命令查看coredns 配…