2023年11月06日
TypeScript的一些实用功能
如今,很难想象一个严肃的基于 JavaScript 的应用程序没有 TypeScript 的支持。接口、元组、泛型和其他特性在 TypeScript 开发者中广为人知。虽然一些高级构造可能需要一定的学习曲线,但它们可以显著增强类型安全性。本文旨在向您介绍一些这些高级特性。
类型守卫
类型守卫帮助我们在条件块内获取有关类型的信息。有一些简单的方法可以使用 in
、typeof
、instanceof
运算符进行类型检查,或者使用相等比较 (===
)。
在这一部分,我想更加关注用户定义的类型守卫。这种守卫充当一个返回布尔值的简单函数。换句话说,返回值是一个类型谓词。让我们来看一个例子,当我们有基本用户信息和带有额外细节的用户时:
type User = { name: string }; type DetailedUser = { name: string; profile: { birthday: string } } function isDetailedUser(user: User | DetailedUser) { return 'profile' in user; } function showDetails(user: User | DetailedUser) { if (isDetailedUser(user)) { console.log(user.profile); // 错误:类型 'User | DetailedUser' 上不存在属性 'profile'。 } }
isDetailedUser
函数返回一个布尔值,但它并没有将这个函数标识为“定义对象类型的布尔值”。
为了实现期望的结果,我们需要稍微更新 isDetailedUser
函数,使用 “user is DetailedUser”
结构:
function isDetailedUser(user: User | DetailedUser): user is DetailedUser { return 'profile' in user; }
索引访问类型
在您的应用程序中可能会出现这样的情况:您有一个大型对象类型,想要创建一个新类型,该类型使用原始类型的一部分。例如,我们的应用程序的一部分需要仅用户配置文件。User['profile']
提取所需的类型并将其分配给 UserProfile
类型。
type User = { id: string; name: string; surname: string; profile: { birthday: string; } } type UserProfile = User['profile'];
如果我们想基于几个属性创建一个类型会怎样?在这种情况下,您可以使用一个名为 Pick
的内置类型。
type FullName = Pick<User, 'name' | 'surname'>; // { name: string; surname: string }
还有许多其他实用类型,比如 Omit
、Exclude
和 Extract
,它们可能对您的应用程序有所帮助。乍一看,它们都是索引类型,但实际上它们都是基于 Mapped
类型构建的。
带有数组的索引类型
您可能遇到过这样的情况,应用程序为您提供了一个联合类型,例如:
type UserRoleType = ‘admin’ | ‘user’ | ‘newcomer’;
然后,在应用程序的另一部分,我们获取用户数据并检查其角色。对于这种情况,我们需要创建一个数组:
const ROLES: UserRoleType[] = [‘admin’, ‘user’, ‘newcomer’]; ROLES.includes(response.user_role);
看起来很累人,不是吗?我们需要在数组内重复联合类型的值。很好的是,索引类型在这里也有帮助。
首先,我们需要使用 const 断言声明我们的数组,以消除重复并创建一个只读元组。
const ROLES = [‘admin’, ‘user’, ‘newcomer’] as const;
然后,使用 typeof
运算符和 number
类型,我们基于数组值创建一个联合类型。
type RolesType = typeof ROLES[number]; // ‘admin’ | ‘user’ | ‘newcomer’;
您可能对这个解决方案感到困惑,但您可能知道,数组是基于数字键的对象构造。这就是为什么在这个例子中,number
被用作索引访问类型。
条件类型和 Infer 关键字
条件类型定义一个依赖于条件的类型。通常,它们与泛型一起使用。根据泛型类型(输入类型),构造选择输出类型。
例如,内置的 NonNullable
TypeScript 类型是基于条件类型构建的。
type NonNullable<T> = T extends null | undefined ? never : T type One = NonNullable<number>; // number type Two = NonNullable<undefined>; // never
infer
关键字与条件类型一起使用,不能在‘extends
’子句之外使用。它充当‘类型变量创建者
’。
我认为通过看一个真实的例子,您会更容易理解它。
案例:检索异步函数的结果类型。
const fetchUser = (): Promise<{ name: string }> => { /* implementation */ }
最简单的解决方案是导入类型声明并将其分配给变量。不幸的是,有些情况下结果声明是写在函数内部的,就像上面的例子一样。
这个问题可以通过两个步骤 解决:
Awaited
实用类型在 TypeScript 4.5 中引入。为了学习目的,让我们看一个简化的变体。
export type Awaited<T> = T extends Promise<infer U> ? U : T;
使用条件类型和 infer
关键字,我们“提取”了承诺的类型并将其分配给 U
名称。这是一种类型变量声明。如果传递的类型与 PromiseLike
泛型兼容,构造将返回保存到 U
名称的原始类型。
- 从异步函数获取值。
使用内置的 ReturnType
提取函数的返回类型和我们的 Awaited
类型,我们实现了期望的结果:
export type Awaited ReturnType<T> = Awaited<Return Type<T>>;