12. TypeScript 高级类型

TypeScript 中的高级类型包括映射类型、条件类型、字面量类型和递归类型等强大结构。这些特性使开发者能够表达类型之间更复杂的关系,从而处理边缘情况,并定义更动态、更灵活的类型系统。

一、映射类型

TypeScript 映射类型(Mapped Types)是一种高级类型工具,它允许我们基于已有的类型创建新的类型。通过遍历已有类型的键(key),并对其进行变换,可以快速构造具有相同结构但属性类型不同的新类型,从而提高代码的灵活性和复用性。

(一) 概念

TypeScript 中的映射类型(Mapped Types)允许你通过转换现有类型的属性来创建新的类型。

  • 它们支持对属性进行修改,例如将属性设为可选(optional)、只读(read-only),或者改变属性的类型。
  • 映射类型有助于减少重复代码,并通过自动化的类型转换提升类型安全性。
  • 它们特别适用于创建现有类型的不同变体,而无需手动重新定义每个属性。

例如下面的代码所示:

type User = {id: number;name: string;email: string;
};type PartialUser = {[P in keyof User]?: User[P];
};

代码解释:

  • PartialUser 是一个新类型,其中 User 类型的每个属性都被标记为可选(optional)。
  • keyof 操作符用于获取 User 类型的所有属性键,而映射类型则会遍历每一个键,并通过 ? 将它们标记为可选属性。

输出:

const user1: PartialUser = { id: 1 }; 
const user2: PartialUser = {}; 
const user3: PartialUser = { id: 2, name: "Alice" }; 

(二) 场景示例

1. 创建只读属性

在 TypeScript 中,可以使用 映射类型(Mapped Types)readonly 关键字创建一个新的类型,使其所有属性变为只读。

type User = {id: number;name: string;email: string;
};type ReadonlyUser = {readonly [P in keyof User]: User[P];
};const user: ReadonlyUser = { id: 1, name: "Alice", email: "alice@example.com" };
user.id = 2;

代码解释:

  • ReadonlyUser 是一个新类型,其中 User 的所有属性都被标记为只读(readonly)。
  • 尝试修改 user 对象的任何属性都会导致编译时错误。

输出:

Error: Cannot assign to 'id' because it is a read-only property.

2. 创建可为空属性

创建可为空属性(Creating Nullable Properties)指的是将一个类型中的所有属性变为可以为 null 的类型。在 TypeScript 中,可以使用映射类型(Mapped Types)结合联合类型(Union Types)来实现这一点。

type Product = {name: string;price: number;inStock: boolean;
};type NullableProduct = {[P in keyof Product]: Product[P] | null;
};const product: NullableProduct = { name: "Laptop", price: null, inStock: true };

代码解释:

  • NullableProduct 是一个新的类型,它使得 Product 类型中的每个属性都可以是其原始类型,或者是 null
  • 这允许属性显式地具有 null 值,用于表示该属性当前没有值或值缺失的情况。

输出:

{ name: "Laptop", price: null, inStock: true }

3. 使用模板字面量重命名属性

使用模板字面量重命名属性(Renaming Properties with Template Literals)是 TypeScript 中映射类型的一种高级用法。它允许我们通过字符串模板语法在创建新类型时动态地改变属性名。

type Person = {firstName: string;lastName: string;
};type PrefixedPerson = {[P in keyof Person as `person${Capitalize<P>}`]: Person[P];
};const person: PrefixedPerson = { personFirstName: "Felix", personLastName: "Raink" };

代码解释:

  • PrefixedPerson 创建了一个新类型,通过在 Person 类型的每个属性名前加上 "person" 前缀,并将原属性名首字母大写。
  • 这个示例演示了如何结合模板字面量类型和 TypeScript 内置的 Capitalize 工具类型来转换属性名称。

输出:

{ personFirstName: "Felix", personLastName: "Raink" }

(三) TypeScript 映射类型使用最佳实践

  • 保持转换简单:避免过于复杂的嵌套转换,以保持代码的可读性和维护的便捷性。
  • 确保类型安全:利用映射类型强制执行一致的属性转换,提高整个代码库的类型安全性。
  • 结合内置工具类型使用:配合 Partial、Readonly、Pick 和 Omit 等内置工具类型,简化常见的类型转换操作。

二、条件类型

在 TypeScript 中,条件类型使开发者能够根据条件创建类型,从而实现更动态和灵活的类型定义。

它们遵循语法 T extends U ? X : Y,意思是如果类型 T 可赋值给类型 U,则类型解析为 X;否则解析为 Y

(一) 概念

条件类型在创建工具类型和进行高级类型操作时特别有用,能够增强代码的复用性和类型安全性。

比如下面这个例子:

type IsString<T> = T extends string ? 'Yes' : 'No';type Test1 = IsString<string>;
type Test2 = IsString<number>;console.log('Test1:', 'Yes');
console.log('Test2:', 'No');
  • 类型别名 IsString 使用条件类型来判断类型 T 是否继承自 string
  • 如果 T 可以赋值给 string,则结果为 'Yes';否则结果为 'No'
  • Test1 被评估为 'Yes',因为 string 继承自 string
  • Test2 被评估为 'No',因为 number 不继承自 string

输出:

Test1: Yes
Test2: No

(二) 场景示例

1. 条件类型约束

条件类型约束允许在条件类型中对泛型类型进行约束,从而实现动态且精确的类型处理。

type CheckNum<T> = T extends number ? T : never;type NumbersOnly<T extends any[]> = {[K in keyof T]: CheckNum<T[K]>;
};const num: NumbersOnly<[4, 5, 6, 8]> = [4, 5, 6, 8];
const invalid: NumbersOnly<[4, 6, "7"]> = [4, 6, "7"];

代码解释:

  • CheckNum<T> 确保只保留数字类型;其他类型则解析为 never
  • NumbersOnly<T> 对数组中的每个元素应用 CheckNum,过滤掉非数字类型。

输出:

Type '"7"' is not assignable to type 'never'.

2. 条件类型中的类型推断

此特性允许在条件类型定义中提取并使用类型,从而实现精确的类型转换。

type ElementType<T> = T extends (infer U)[] ? U : never;const numbers: number[] = [1, 2, 3];
const element: ElementType<typeof numbers> = numbers[0];
const invalidElement: ElementType<typeof numbers> = "string";

代码解释:

  • ElementType<T> 使用 infer 关键字从数组中提取元素类型。
  • 变量 element 被正确推断为 number;尝试赋值为字符串则无效。

输出:

Type 'string' is not assignable to type 'number'.

3. 分布式条件类型

分布式条件类型(Distributive Conditional Types)是 TypeScript 条件类型的一种特性,当条件类型作用于联合类型时,会将条件应用到联合类型的每个成员上,然后将结果合并成新的联合类型。

type Colors = 'red' | 'blue' | 'green';type ColorClassMap = {red: 'danger';blue: 'primary';green: 'success';
};type MapColorsToClasses<T extends string> = T extends keyof ColorClassMap? { [K in T]: ColorClassMap[T] }: never;const redClass: MapColorsToClasses<Colors> = { red: 'danger' };
const invalidClass: MapColorsToClasses<Colors> = { yellow: 'warning' };

代码解释:

  • MapColorsToClasses<T> 会检查类型 T 是否匹配 ColorClassMap 中的某个键,并将其映射为对应的值。
  • 'yellow' 这样无效的颜色会被拒绝,因为它不在 ColorClassMap 中定义。

输出:

Type '{ yellow: "warning"; }' is not assignable to type 'never'.

(三) TypeScript 条件类型的最佳实践

  • 使用条件类型创建灵活且可复用的类型定义。
  • 将条件类型与泛型结合使用,以增强适应性。
  • 在复杂场景中利用 infer 关键字实现类型推断。

三、字面量类型

TypeScript 的字面量类型允许开发者为变量、函数参数或属性指定精确的值,通过确保变量只能持有预定义的值来增强类型安全性。

  • 允许变量具有特定且精确的值。
  • 通过限制允许的值范围,提高代码的可靠性。

以下是 TypeScript 中字面量类型的几种类型:

(一) 字符串字面量类型

字符串字面量类型允许变量只接受特定的一组字符串值。

type Direction = "Up" | "Down" | "Left" | "Right";let move: Direction;move = "Up"; // 有效赋值
// move = "Forward"; // 错误:类型 '"Forward"' 不能赋值给类型 'Direction'
  • Direction 类型只能是指定的字符串字面量之一:“Up”、“Down”、“Left”或“Right”。
  • 赋值为该集合之外的任何值都会导致编译时错误。

(二) 数字字面量类型

数字字面量类型限制变量只能取特定的一组数值。

type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;function rollDice(): DiceRoll {return 4; // 有效的返回值// return 7; // 错误:类型 '7' 不能赋值给类型 'DiceRoll'
}
  • DiceRoll 类型只能是 1 到 6 之间的数字之一。
  • 返回任何不在该范围内的值都会导致编译错误。

(三) 布尔字面量类型

布尔字面量类型限制变量只能是布尔值 true 或 false。

type Success = true;function operation(): Success {return true; // 合法的返回值// return false; // 错误:类型 'false' 不能赋值给类型 'true'
}
  • Success 类型严格限定为 true,返回 false 会导致编译时错误。

(四) TypeScript 字面量类型的最佳实践

  • 使用字面量类型指定精确值:定义变量时使用字面量类型,将其限制为特定的预设值,从而提升代码的可预测性。
  • 结合联合类型使用:利用联合类型让变量能接受有限的多个字面量值,增强类型安全性。
  • 利用类型别名:为复杂的字面量类型组合创建类型别名,简化代码结构并提升可读性。

四、模板字面量类型

TypeScript 中的模板字面量类型允许通过使用模板字面量语法,将已有的字符串字面量类型组合,构造出新的字符串字面量类型。

(一) 概念

它们支持通过在模板字符串中嵌入联合类型或其他字面量类型,创建复杂的字符串模式。
该特性提升了类型安全性,使开发者可以在类型层面定义并强制执行特定的字符串格式。

示例代码:

type Size = "small" | "medium" | "large";
type SizeMessage = `The selected size is ${Size}.`;let message: SizeMessage;message = "The selected size is small.";  // 有效
message = "The selected size is extra-large.";  // 报错

代码解释:

  • Size 是一个联合类型,表示可能的尺寸值。
  • SizeMessage 是一个模板字面量类型,通过嵌入 Size,构造出具体的字符串模式。
  • 变量 message 只能被赋值为符合 SizeMessage 模式的字符串。

错误信息示例:

Type '"The selected size is extra-large."' is not assignable to type 'SizeMessage'.

(二) 场景示例

1. 使用 TypeScript 字面量定义路径

type ApiEndpoints = "users" | "posts" | "comments";
type ApiPath = `/api/${ApiEndpoints}`;const userPath: ApiPath = "/api/users";
const invalidPath: ApiPath = "/api/unknown";
  • ApiEndpoints 是一个联合类型,表示可能的 API 端点名称。
  • ApiPath 是一个模板字面量类型,动态构造出以 /api/ 开头,后接 ApiEndpoints 中任意值的字符串模式。
  • userPath 是有效的,因为它符合构造的模式;而 invalidPath 会报错。

错误信息示例:

Type '"/api/unknown"' is not assignable to type 'ApiPath'.

2. 使用模板字面量格式化消息

type Status = "success" | "error" | "loading";
type StatusMessage = `The operation is ${Status}.`;const successMessage: StatusMessage = "The operation is success.";
const invalidMessage: StatusMessage = "The operation is pending.";
  • Status 是一个联合类型,表示操作的可能状态。
  • StatusMessage 构造字符串模式,用于描述操作状态。
  • successMessage 是有效的,因为它符合模式;而 invalidMessage 报错,因为 "pending" 不属于 Status 类型。

错误信息示例:

Type '"The operation is pending."' is not assignable to type 'StatusMessage'.

五、递归类型

TypeScript 为 JavaScript 添加了强类型支持。递归类型定义了可以自我引用的类型,适用于树形结构或嵌套对象。实用工具类型(Utility Types)则简化了类型的修改,比如将属性设为可选或只读。这些工具帮助我们写出更清晰、更灵活的代码。

(一) TypeScript 中的递归类型

递归类型是在其定义中引用自身的类型。这使得我们能够建模复杂的数据结构,比如树、链表和嵌套对象,其中类型可以嵌套自身。

  • 允许定义自我引用的类型。
  • 适合表示层级或嵌套数据。
  • 必须使用 TypeScript 的类型别名(type alias)或接口(interface)来定义递归结构。

1. 语法示例

下面是一个表示树结构的简单递归类型:

type TreeNode = {value: number;children?: TreeNode[];
};
  • TreeNode 类型包含一个 value 属性和一个可选的 children 属性。
  • children 是一个 TreeNode 数组,支持嵌套结构。

2. 使用递归类型示例

const tree: TreeNode = {value: 1,children: [{ value: 2 },{value: 3,children: [{ value: 4 },{ value: 5 }]}]
};

在此示例中,tree 表示一个层级结构,节点可以有子节点,子节点又可以有自己的子节点,依此类推。

3. 递归类型的优点

  • 建模复杂结构:递归类型方便表示层级结构。
  • 类型安全:TypeScript 确保递归结构在每个嵌套层级都符合正确的类型。
  • 类型复用:递归类型可以在多种场景中复用相同的结构定义。

(二) TypeScript 中的实用工具类型(Utility Types)

TypeScript 内置了一些实用工具类型,提供了修改或转换其他类型的现成功能,简化常见的类型操作,提高开发效率。

1. 常见实用工具类型

Partial<T>

将类型 T 的所有属性设为可选。
适用于创建部分属性可缺失的对象。

type Partial<T> = {[P in keyof T]?: T[P];
};
Required<T>

将类型 T 的所有属性设为必需。
确保对象中的所有属性必须存在。

type Required<T> = {[P in keyof T]?: T[P];
};
Readonly<T>

将类型 T 的所有属性设为只读。
防止初始化后修改对象属性。

type Readonly<T> = {readonly [P in keyof T]: T[P];
};

Pick<T, K>

从类型 T 中挑选出属性 K 的子集。
适用于从复杂类型中选取部分属性。

type Pick<T, K extends keyof T> = {[P in K]: T[P];
};

Omit<T, K>

从类型 T 中剔除属性 K。
用于排除不需要的属性。

type Omit<T, K extends keyof T> = {[P in Exclude<keyof T, K>]: T[P];
};

Record<K, T>

构造一个对象类型,其属性键为 K,值为 T。
用于定义类似映射的数据结构。

type Record<K extends keyof any, T> = {[P in K]: T;
};

Exclude<T, U>

从类型 T 中排除可赋值给 U 的类型。
用于过滤联合类型中的部分类型。

type Exclude<T, U> = T extends U ? never : T;

Extract<T, U>

从类型 T 中提取可赋值给 U 的类型。
用于缩小联合类型范围。

type Extract<T, U> = T extends U ? T : never;

NonNullable<T>

从类型 T 中排除 nullundefined
确保值不是 null 或 undefined。

type NonNullable<T> = T extends null | undefined ? never : T;

2. 实用工具类型的优点

  • 简化类型转换:内置的工具类型方便对类型结构进行变换(如变成可选、必需等)。
  • 提升代码可读性:使用简洁的类型关键字表达转换意图,使代码更清晰。
  • 提高开发效率:避免重复手写复杂类型定义,减少错误。

通过递归类型和实用工具类型,TypeScript 为复杂数据结构和类型操作提供了强大而灵活的支持,助力开发者编写更安全、更高效的代码。

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

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

相关文章

韩国证券交易所(KRX)全生态接入系统技术白皮书

核心价值&#xff1a;为全球最活跃的衍生品市场&#xff08;日均交易量480亿美元&#xff09;提供 5μs延迟引擎全合规认证&#xff0c;助力中资机构抢占韩国78%衍生品交易份额 一、KRX市场机遇与准入壁垒 1.1 核心数据锚定&#xff08;2025Q2&#xff09; 指标数值全球竞争力…

【Clickhouse系列】增删改查:对比mysql

目录 1. 写入操作 (INSERT) 2. 删除操作 (DELETE) 3. 更新操作 (UPDATE) 4. 查询操作 (SELECT) 5. 总结对比表&#xff1a; 6. 参考链接 核心哲学差异&#xff1a; MySQL&#xff1a; 面向在线事务处理。核心目标是保证数据的强一致性、原子性和低延迟的单行操作&#x…

低压电工作业中,如何正确选用熔断器的额定电流?

在低压电工作业中&#xff0c;正确选用熔断器额定电流需综合考虑负载类型、额定电流等因素&#xff0c;具体方法如下&#xff1a; 照明电路&#xff1a;对于白炽灯负载&#xff0c;熔体额定电流可按被保护电路上所有白炽灯工作电流之和的 1.1 倍选取。若是日光灯和高压水银荧…

MySQL:索引优化实战技巧

目录 一、前言 二、基础知识回顾 三、索引设计优化 1.遵循最左匹配原则&#xff0c;合理设计联合索引顺序 2.利用覆盖索引避免回表查询 3.针对字符串列使用前缀索引 4.合理使用复合索引替代多个单列索引 5.使用前缀索引优化模糊查询的左匹配 四、索引使用优化 1.避免在…

开关电源计算辅助软件SMPSKIT V10.3

资料下载地址&#xff1a;开关电源计算辅助软件SMPSKIT V10.3 SMPSKIT &#xff1a; 内置一些常见IC的计算 内置绝大多数磁芯数据 内置变压器分层计算器 可用户编程功能 包含绝大多数拓…

OpenHarmony应用开发-全量包的使用

文章目录 一、下载full-sdk二、替换本地对应版本的SDK1.查看本地SDK安装目录2.替换对应的SDK版本 三、升级APL权限为系统权限&#xff08;升级后便可使用系统接口&#xff09;四、重启IDE并重新进行应用签名总结 一、下载full-sdk 可以在官方提供的“每日构建”中搜索对应版本…

sudo安装pip包的影响

使用 sudo 安装的 pip 包和不使用 sudo 安装的 pip 包在 Ubuntu 20.04 上有以下几个主要区别&#xff1a; 1. 安装位置&#xff1a; 使用 sudo: 包会被安装到系统级别的 Python 环境中&#xff0c;通常是 /usr/local/lib/python3.8/dist-packages/ 或 /usr/lib/python3/dist-…

uniapp 多图上传,加水印功能(全平台通用)

多图上传和水印都是比较难得&#xff0c;特别是有的api只支持在小程序用&#xff0c;h5不给用 效果图 普通的多图上传 // 多图上传 // count&#xff1a;最大数量 export function headerUploads0(count 9, orderNumber , watermarkInfo) {return new Promise((resolve, r…

【appium】5. Appium WebDriver 支持的常用方法汇总

下面是一个完整的 Appium WebDriver 支持的常用方法汇总&#xff0c;并附上典型用法示例。 一、元素查找方法/元素操作方法 ✅ 使用 find_element() 和 find_elements() from appium import webdriver from appium.webdriver.common.appiumby import AppiumBy# 单个元素查找 …

FreeRTOS 介绍、使用方法及应用场景

一、FreeRTOS 概述 FreeRTOS 是一款广泛应用于嵌入式系统的实时操作系统&#xff08;RTOS&#xff09;&#xff0c;具有开源、可移植、可裁剪、轻量级等显著特点。它最初由 Richard Barry 开发&#xff0c;如今已成为全球开发者在物联网、工业控制、消费电子等领域的热门选择&a…

深度解析 Caffeine:高性能 Java 缓存库

1. Caffeine 简介 Caffeine 是一个基于 Java 8 的高性能本地缓存库&#xff0c;由 Ben Manes 开发&#xff0c;旨在替代 Google Guava Cache&#xff0c;提供更优的缓存策略、更高的吞吐量和更灵活的配置。 核心优势 ✅ 卓越的性能&#xff1a;采用优化的数据结构&#xff0…

创客匠人赋能创始人 IP 打造:健康行业知识变现案例深度解析

在知识服务行业蓬勃发展的当下&#xff0c;创始人 IP 打造已成为知识变现的核心驱动力。创客匠人近期披露的陪跑案例显示&#xff0c;通过系统化的线上线下联动运营&#xff0c;传统行业从业者可高效实现 IP 价值转化。以亓黄中医科技创始人吴丰言老师为例&#xff0c;其在创客…

64、最小路径和

题目&#xff1a; 解答&#xff1a; 简单dp。 定义&#xff1a;dp[i][j]为到达(i,j)所需要的最短路程 初始化&#xff1a;dp[0][0]grid[0][0]&#xff0c;同时对第一行和第一列的&#xff0c;第i个就是前i个之和加上自身 递归&#xff1a;dp[i][j]min(dp[i-1][j],dp[i][j-1…

获取连接通义千问大语言模型配置信息的步骤:api_key、api_url

一、注册并开通通义千问API服务 1. 注册阿里云账号 访问 阿里云官网点击右上角"免费注册"&#xff0c;按指引完成账号注册和实名认证 2. 开通通义千问API服务 进入 通义千问API产品页点击"立即开通"&#xff0c;按提示完成服务开通&#xff08;部分服务…

汽车加气站操作工考试题库含答案【最新】

1.天然气的主要成分是&#xff08;&#xff09;。 A. 乙烷 B. 乙烯 C. 甲烷 D. 乙炔 答案&#xff1a;C 2.CNG 加气站中&#xff0c;加气机的加气软管应&#xff08;&#xff09;进行检查。 A. 每天 B. 每周 C. 每月 D. 每季度 答案&#xff1a;A 3.储气罐的安全阀应&#xf…

显示任何结构的数组对象数据【向上自动滚动】

显示任何结构的数组对象数据 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>地图编辑软件 - 数…

GPIO模式详解

一、GPIO的八种模式 GPIO支持4种输入模式&#xff08;浮空输入、上拉输入、下拉输入、模拟输入&#xff09;和4种输出模式&#xff08;开漏输出、开漏复用输出、推挽输出、推挽复用输出&#xff09;。 GPIO_Mode_AIN模拟输入GPIO_Mode_IN_FLOATING浮空输入GPIO_Mode_IPD下拉输…

django rest_framework 自定义403 Forbidden错误页面

django本来有是可以很方便自定义HTTP错误页面的&#xff0c;网上资料一大把。核心是在项目的urls代码中增加handler403的定义&#xff0c;比如&#xff1a; handler403 "app.views.your_custom_view" 404&#xff0c;500都是一样的&#xff0c;重新定义handler404…

Kafka Streams架构深度解析:从并行处理到容错机制的全链路实践

在流处理技术领域&#xff0c;Kafka Streams以其轻量级架构与Kafka生态的深度整合能力脱颖而出。作为构建在Kafka生产者/消费者库之上的流处理框架&#xff0c;它通过利用Kafka原生的分区、副本与协调机制&#xff0c;实现了数据并行处理、分布式协调与容错能力的无缝集成。本文…

【嵌入式硬件实例】-555定时器控制舵机/伺服电机

555定时器控制舵机/伺服电机 文章目录 555定时器控制舵机/伺服电机1、555定时器介绍2、舵机/伺服电机介绍3、硬件准备与接线使用 555 定时器 IC 的伺服电机控制器和测试仪电路是一个简单的电路,可用于生成操作伺服电机所需的控制信号。该电路允许我们通过按下按钮手动驱动/控制…