Typescript学习教程,从入门到精通,TypeScript 泛型与类型操作详解(一)(16)

TypeScript 泛型与类型操作详解(一)

TypeScript 提供了强大的类型系统,其中泛型(Generics)和类型操作(Type Manipulation)是其核心特性之一。本文将详细介绍 TypeScript 中的泛型及其相关概念,并通过案例代码进行说明。


一、泛型简介

泛型允许在定义函数、接口或类时,不预先指定具体的类型,而是在使用时指定类型。这种方式提高了代码的复用性和灵活性。

为什么使用泛型

  1. 提高代码重用性
  2. 提供更好的类型安全性
  3. 减少使用 any 类型的需要
  4. 在编译时捕获类型错误

1.1 形式类型参数与实际类型参数

  • 形式类型参数(Type Parameters):在泛型定义中声明的类型占位符,通常使用 <T><T, U> 等形式。
  • 实际类型参数(Type Arguments):在使用泛型时,传入的具体类型。

示例:

// 定义一个泛型函数,形式类型参数为 T
function identity<T>(arg: T): T {return arg;
}// 使用泛型函数,实际类型参数为 number
let output = identity<number>(42); // output 的类型为 number// 也可以让 TypeScript 推断类型
let output2 = identity("Hello, TypeScript!"); // output2 的类型为 string

1.2 泛型约束

有时需要对泛型参数进行约束,限制其必须符合某些条件。可以使用 extends 关键字实现。

示例:

// 定义一个接口,描述必须具有 length 属性的类型
interface Lengthwise {length: number;
}// 使用泛型约束,限制 T 必须符合 Lengthwise 接口
function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length); // 现在可以安全地使用 length 属性return arg;
}// 正确使用
loggingIdentity("Hello"); // string 符合 Lengthwise 接口// 错误使用,会在编译时报错
// loggingIdentity(42); // number 不符合 Lengthwise 接口

二、泛型函数

泛型函数允许函数在调用时指定类型参数,从而实现更灵活的参数和返回值类型。

示例:

// 泛型函数,返回第一个元素
function getFirstElement<T>(arr: T[]): T {return arr[0];
}let firstNumber = getFirstElement<number>([1, 2, 3]); // firstNumber 的类型为 number
let firstString = getFirstElement<string>(["a", "b", "c"]); // firstString 的类型为 string

三、泛型接口

接口也可以是泛型的,用于定义泛型函数的类型或泛型对象的结构。

示例:

// 定义一个泛型接口,描述一个包含键值对的对象
interface Pair<K, V> {key: K;value: V;
}// 使用泛型接口
let user: Pair<string, number> = {key: "age",value: 30
};

四、泛型类型别名

类型别名可以使用泛型来创建可复用的复杂类型。

示例:

// 定义一个泛型类型别名,描述一个数组或对象
type Container<T> = T[] | { value: T };// 使用泛型类型别名
let numberContainer: Container<number> = [1, 2, 3];
let stringContainer: Container<string> = { value: "Hello" };

五、泛型类

类也可以是泛型的,用于创建可复用的组件。

示例:

// 定义一个泛型类,表示一个栈
class Stack<T> {private elements: T[] = [];push(element: T) {this.elements.push(element);}pop(): T | undefined {return this.elements.pop();}peek(): T | undefined {return this.elements[this.elements.length - 1];}
}// 使用泛型类
let numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.peek()); // 输出: 2let stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.peek()); // 输出: b

六、局部类型

局部类型是指在函数或块作用域内定义的类型。

示例:

function process<T>(value: T) {// 定义一个局部类型type Wrapped = { wrapped: T };let wrappedValue: Wrapped = { wrapped: value };console.log(wrappedValue);
}process("Hello"); // 输出: { wrapped: "Hello" }

七、联合类型

联合类型表示一个值可以是几种类型之一,使用 | 分隔。

示例:

function printId(id: number | string) {console.log("ID:", id);
}printId(101); // 输出: ID: 101
printId("202"); // 输出: ID: 202

7.1 联合类型字面量

联合类型字面量是指字面量值的联合。

示例:

type Direction = "left" | "right" | "up" | "down";function move(direction: Direction) {console.log("Moving", direction);
}move("left"); // 输出: Moving left
// move("forward"); // 编译错误

7.2 联合类型的类型成员

联合类型的每个成员类型必须符合所有成员类型的共有属性。

示例:

interface Bird {fly(): void;layEggs(): void;
}interface Fish {swim(): void;layEggs(): void;
}function getSmallPet(): Bird | Fish {// ...
}let pet = getSmallPet();
pet.layEggs(); // 正确
// pet.fly(); // 错误: Fish 类型没有 fly 方法

八、交叉类型

交叉类型表示一个值同时符合几种类型,使用 & 分隔。

示例:

interface Person {name: string;
}interface Employee {employeeId: number;
}type PersonEmployee = Person & Employee;let personEmployee: PersonEmployee = {name: "Alice",employeeId: 123
};

8.1 交叉类型字面量

交叉类型字面量是指字面量值的交叉。

示例:

type Color = "red" & "blue"; // 实际上是一个空类型,因为没有值同时是 "red" 和 "blue"let color: Color;
// 无法实例化,因为没有值符合 Color 类型

8.2 交叉类型的类型成员

交叉类型的成员类型必须符合所有成员类型的属性。

示例:

interface A {a: string;
}interface B {b: number;
}type C = A & B;let c: C = {a: "Hello",b: 42
};

九、交叉类型与联合类型

交叉类型和联合类型可以组合使用,以创建更复杂的类型。

示例:

interface Dog {bark(): void;
}interface Cat {meow(): void;
}type DogCat = Dog & Cat;function getDogCat(): DogCat {return {bark() {console.log("Woof!");},meow() {console.log("Meow!");}};
}let pet: DogCat = getDogCat();
pet.bark(); // 输出: Woof!
pet.meow(); // 输出: Meow!

十、索引类型

索引类型允许在泛型中使用动态属性访问。

10.1 索引类型查询

使用 keyof 操作符获取一个类型的键的联合类型。

示例:

interface Person {name: string;age: number;address: string;
}type PersonKeys = keyof Person; // "name" | "age" | "address"

10.2 索引访问类型

使用索引访问类型获取某个属性的类型。

示例:

type NameType = Person["name"]; // string
type AgeType = Person["age"]; // number

10.3 索引类型的应用

结合泛型和索引类型,实现更灵活的类型操作。

示例:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {return obj[key];
}let person: Person = {name: "Bob",age: 25,address: "123 Main St"
};let name = getProperty(person, "name"); // name 的类型为 string
let age = getProperty(person, "age"); // age 的类型为 number

十一、映射对象类型

映射对象类型允许动态地创建对象类型,基于已有的类型进行转换。

11.1 映射对象类型声明

使用 in 关键字和 keyof 操作符来声明映射对象类型。

示例:

type ReadonlyPerson = {readonly [K in keyof Person]: Person[K]
};let readonlyPerson: ReadonlyPerson = {name: "Charlie",age: 30,address: "456 Elm St"
};// readonlyPerson.age = 31; // 错误: 属性 age 是只读的

11.2 映射对象类型解析

映射对象类型通过遍历键并应用转换函数来生成新的类型。

示例:

type Stringify<T> = {[K in keyof T]: string;
};type StringifiedPerson = Stringify<Person>;let stringifiedPerson: StringifiedPerson = {name: "Dave",age: "25",address: "789 Oak St"
};

11.3 映射对象类型的应用

结合泛型和映射对象类型,实现更复杂的类型操作。

示例:

// 定义一个泛型函数,将对象的属性值转换为字符串
function stringifyValues<T>(obj: T): { [K in keyof T]: string } {let result = {} as { [K in keyof T]: string };for (let key in obj) {result[key] = String(obj[key]);}return result;
}let person: Person = {name: "Eve",age: 22,address: "321 Pine St"
};let stringifiedPerson = stringifyValues(person);
// stringifiedPerson 的类型为 { name: string; age: string; address: string }

十二、同态映射对象类型

同态映射对象类型是指在映射过程中保持原有类型的结构。

示例:

// 定义一个泛型函数,创建一个只读版本的映射对象类型
function makeReadonly<T>(obj: T): { readonly [K in keyof T]: T[K] } {return Object.freeze(obj);
}let person: Person = {name: "Frank",age: 28,address: "654 Cedar St"
};let readonlyPerson = makeReadonly(person);
// readonlyPerson 的属性是只读的
// readonlyPerson.age = 29; // 错误: 属性 age 是只读的

总结

TypeScript 的泛型与类型操作提供了强大的工具,使得开发者能够编写灵活、可复用的代码。通过理解和掌握这些概念,可以显著提升代码质量和开发效率。

案例代码汇总

以下是上述各个部分的完整代码示例:

// 泛型函数
function identity<T>(arg: T): T {return arg;
}let output = identity<number>(42);
let output2 = identity("Hello, TypeScript!");// 泛型约束
interface Lengthwise {length: number;
}function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length);return arg;
}// 泛型接口
interface Pair<K, V> {key: K;value: V;
}let user: Pair<string, number> = {key: "age",value: 30
};// 泛型类型别名
type Container<T> = T[] | { value: T };let numberContainer: Container<number> = [1, 2, 3];
let stringContainer: Container<string> = { value: "Hello" };// 泛型类
class Stack<T> {private elements: T[] = [];push(element: T) {this.elements.push(element);}pop(): T | undefined {return this.elements.pop();}peek(): T | undefined {return this.elements[this.elements.length - 1];}
}let numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.peek());let stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.peek());// 局部类型
function process<T>(value: T) {type Wrapped = { wrapped: T };let wrappedValue: Wrapped = { wrapped: value };console.log(wrappedValue);
}process("Hello");// 联合类型
function printId(id: number | string) {console.log("ID:", id);
}printId(101);
printId("202");// 联合类型字面量
type Direction = "left" | "right" | "up" | "down";function move(direction: Direction) {console.log("Moving", direction);
}move("left");// 交叉类型
interface Person {name: string;
}interface Employee {employeeId: number;
}type PersonEmployee = Person & Employee;let personEmployee: PersonEmployee = {name: "Alice",employeeId: 123
};// 索引类型
interface Person {name: string;age: number;address: string;
}type PersonKeys = keyof Person;function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {return obj[key];
}let person: Person = {name: "Bob",age: 25,address: "123 Main St"
};let name = getProperty(person, "name");
let age = getProperty(person, "age");// 映射对象类型
type ReadonlyPerson = {readonly [K in keyof Person]: Person[K]
};let readonlyPerson: ReadonlyPerson = {name: "Charlie",age: 30,address: "456 Elm St"
};// 映射对象类型解析
type Stringify<T> = {[K in keyof T]: string;
};type StringifiedPerson = Stringify<Person>;let stringifiedPerson: StringifiedPerson = {name: "Dave",age: "25",address: "789 Oak St"
};// 映射对象类型的应用
function stringifyValues<T>(obj: T): { [K in keyof T]: string } {let result = {} as { [K in keyof T]: string };for (let key in obj) {result[key] = String(obj[key]);}return result;
}let person: Person = {name: "Eve",age: 22,address: "321 Pine St"
};let stringifiedPerson2 = stringifyValues(person);// 同态映射对象类型
function makeReadonly<T>(obj: T): { readonly [K in keyof T]: T[K] } {return Object.freeze(obj);
}let person2: Person = {name: "Frank",age: 28,address: "654 Cedar St"
};let readonlyPerson2 = makeReadonly(person2);

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

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

相关文章

电网即插即用介绍

一、统一设备信息模型与标准接口 实现即插即用功能的基础在于建立统一的设备信息模型。不同厂家生产的各类电网设备&#xff0c;其内部结构、通信协议、数据格式等往往千差万别。通过制定统一的设备信息模型&#xff0c;能够对设备的各种属性、功能以及接口进行标准化定义&…

核心机制:确认应答和超时重传

核心机制一:确认应答 实现让发送方知道接受方是否收到数据 发送方发送了数据之后,接受方,一旦接收到了,就会给发送方返回一个"应答报文"告诉发送方"我已经收到了数据" 网络上会出现"后发先至"的情况 为了解决上述问题,就引入了"序号和确…

spring openfeign

pom <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http…

从零到一选择AI自动化平台:深度解析n8n、Dify与Coze

随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;越来越多的企业和开发者开始探索AI驱动的自动化解决方案。面对市场上琳琅满目的平台&#xff0c;如何选择适合自己的AI自动化工具成为了一个重要的问题。在这篇文章中&#xff0c;我们将从功能、应用场景、易…

“以光惠算”走进校园,湖北大学用F5G-A全光网赋能智慧校园

SUN的联合创始人约翰盖奇&#xff0c;曾在1984年提出过一个大胆的猜想——“网络就是计算机”。 到了大模型时代&#xff0c;40多年前的猜想被赋予了新的内涵。大模型训练和推理所需的资源&#xff0c;远超单台计算机的承载能力&#xff0c;涌现出了新的网络范式&#xff1a;大…

飞牛fnNAS的Docker应用之迅雷篇

目录 一、“迅雷”应用安装 二、启动迅雷 三、迅雷账号登录 四、修改“迅雷”下载保存路径 1、下载路径准备 2、停止“迅雷”Docker容器 3、修改存储位置 4、重新启动Docker容器 5、再次“启用”迅雷 五、测试 1、在PC上添加下载任务 2、手机上管理 3、手机添加下…

编程技能:格式化打印01,vsprintf 函数族简介

专栏导航 本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏&#xff0c;故划分为两个专栏导航。读者可以自行选择前往哪个专栏。 &#xff08;一&#xff09;WIn32 专栏导航 上一篇&#xff1a;编程技能&#xff1a;字符串函数14&#xff0c;memset 回到目录…

PECVD 生成 SiO₂ 的反应方程式

在PECVD工艺中&#xff0c;沉积氧化硅薄膜以SiH₄基与TEOS基两种工艺路线为主。 IMD Oxide&#xff08;USG&#xff09; 这部分主要沉积未掺杂的SiO₂&#xff0c;也叫USG&#xff08;Undoped Silicate Glass&#xff09;&#xff0c;常用于IMD&#xff08;Inter-Metal Diele…

[IMX] 10.串行外围设备接口 - SPI

代码链接&#xff1a;GitHub - maoxiaoxian/imx 参考资料&#xff1a; https://zhuanlan.zhihu.com/p/290620901 SPI协议详解 - bujidao1128 - 博客园 SPI总线协议及SPI时序图详解 - Ady Lee - 博客园 目录 1.SPI 简介 2.I.MX6U ECSPI 简介 2.1.控制寄存器 1 - ECSPIx_CO…

基于Docker和YARN的大数据环境部署实践最新版

基于Docker和YARN的大数据环境部署实践 目的 本操作手册旨在指导用户通过Docker容器技术&#xff0c;快速搭建一个完整的大数据环境。该环境包含以下核心组件&#xff1a; Hadoop HDFS/YARN&#xff08;分布式存储与资源调度&#xff09;Spark on YARN&#xff08;分布式计算…

Java设计模式之中介者模式详解

Java设计模式之中介者模式详解 一、中介者模式核心思想 核心目标&#xff1a;通过中介对象封装一组对象间的交互&#xff0c;将网状的对象关系转变为星型结构。如同机场控制塔协调所有飞机的起降&#xff0c;避免飞机之间直接通信导致的混乱。 二、中介者模式类图&#xff08;…

ArcGIS应用指南:基于网格与OD成本矩阵的交通可达性分析

随着城市化进程的加速,交通系统的效率和公平性日益成为影响居民生活质量的关键因素之一。在这一背景下,如何科学评估城市区域内的交通可达性,成为了城市规划、交通管理和公共政策制定中的重要议题。作为中国东南沿海的重要港口城市,厦门以其独特的地理优势和快速的城市发展…

基于NXP例程学习CAN UDS刷写流程

文章目录 前言1.概述1.1 诊断报文 2.协议数据单元(N_PDU)2.1 寻址信息&#xff08;N_AI&#xff09;2.1.1 物理寻址2.1.2 功能寻址2.1.3 常规寻址&#xff08;Normal addressing&#xff09;2.1.4 常规固定寻址&#xff08;Normal fixed addressing&#xff09;2.1.5 扩展寻址&…

近期手上的一个基于Function Grap(类AWS的Lambda)小项目的改造引发的思考

函数式Function是云计算里最近几年流行起来的新的架构和模式&#xff0c;因为它不依赖云主机&#xff0c;非常轻量&#xff0c;按需使用&#xff0c;甚至是免费使用&#xff0c;特别适合哪种数据同步&#xff0c;数据转发&#xff0c;本身不需要保存数据的业务场景&#xff0c;…

什么是 SQL 注入?如何防范?

什么是 SQL 注入?如何防范? 1. SQL 注入概述 1.1 基本定义 SQL 注入(SQL Injection)是一种通过将恶意SQL 语句插入到应用程序的输入参数中,从而欺骗服务器执行非预期SQL命令的攻击技术。攻击者可以利用此漏洞绕过认证、窃取数据甚至破坏数据库。 关键结论:SQL 注入是O…

高德地图应用OceanBase单元化构建下一代在线地图服务

IEEE International Conference on Data Engineering (ICDE) 是数据库和数据工程领域的顶级学术会议之一&#xff08;与SIGMOD、VLDB并成为数据库三大顶会&#xff09;&#xff0c;自1984年首次举办以来&#xff0c;每年举办一次。ICDE涵盖广泛的主题&#xff0c;包括数据库系统…

Vue3中Element-Plus中el-input及el-select 边框样式

如果不需要显示下边框&#xff0c;纯无边框直接将 【border-bottom: 1px solid #C0C4CC; 】注掉或去掉即可。 正常引用组件使用即可&#xff0c;无须自定义样式&#xff0c;最终效果CSS样式。 <style scoped> /* 输入框的样式 */ :deep(.el-input__wrapper) { box-sha…

如何做好一份技术文档:从信息孤岛到知识图谱的进阶之路

如何做好一份技术文档&#xff1a;从信息孤岛到知识图谱的进阶之路 在软件开发的漫长征程中&#xff0c;技术文档如同隐藏在代码丛林中的路标&#xff0c;不仅指引着开发团队的前行方向&#xff0c;更在产品迭代的岁月里构筑起知识传承的桥梁。一份优质的技术文档&#xff0c;既…

Docker Compose使用自定义用户名密码启动Redis

通常我们使用下面的命令来启动 redis 容器&#xff0c;此时连接 Redis 的时候是不需要用户认证的 sudo docker run -d --name my-redis -p 6379:6379 redis此时我们可以使用 redis-server --requirepass "mypassword" 来指定默认用户&#xff08;default&#xff09…

1.什么是node.js、npm、vue

一、Node.js 是什么&#xff1f; &#x1f63a; 定义&#xff1a; Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境&#xff0c;让你可以在浏览器之外运行 JavaScript 代码&#xff0c;主要用于服务端开发。 &#x1f63a;从计算机底层说&#xff1a;什么是“运…