TypeScript 是 JavaScript 的一个超集,添加了类型系统和对 ES6 及以后版本的支持。以其静态类型系统为开发者提供了一种更结构化、更安全的方式来编写代码。TypeScript的类型系统不仅包括基本类型,还提供了一套强大的工具类型(Utility Types)。
它们允许开发者以声明性的方式操作和构建复杂的类型结构。无论是Vue3 + Ts
还是React + Ts
,在我们平时进行项目开发时,灵活运用一些工具类型就会显得游刃有余。
本文将介绍一些常用的TypeScript工具类型,以及如何使用它们来提升代码的类型安全性。废话不多说,走你!
Awaited<Type>
用于处理异步操作中 await
表达式的结果类型。当你对一个 Promise
或任何可以 await
的值使用 await
关键字时,Awaited<Type>
会展开这个 Promise
的解析类型。等待一个 Promise 解析并返回其结果。可以确保 await
表达式的结果总是被正确地类型化,从而提高代码的健壮性和可维护性。
- 基本用法
当你 await
一个 Promise
或 Thenable
对象时,Awaited<Type>
会提取出这个 Promise
的成功状态的类型。如果 Promise
可能解析为多种类型,Awaited<Type>
将尝试合并这些类型。
// 假设我们有一个异步函数,返回一个 Promise
async function fetchNumber(): Promise<number> {
return 42;
}
// 使用 Awaited<Type> 来获取 await 表达式的结果类型
type A = Awaited<Promise<number | undefined>>;
// A 的类型是 number | undefined
- 合并类型
如果 Promise
可能解析为多种类型,Awaited<Type>
会尝试合并这些类型。例如,如果 Promise
可能解析为 string
或 null
,Awaited<Type>
将结果类型合并为 string | null
。
async function fetchStringOrNull(): Promise<string | null> {
return "Hello"; // 或者可能返回 null
}
type A = Awaited<ReturnType<typeof fetchStringOrNull>>;
// A 的类型是 string | null
- 与 TypeScript 的类型守卫结合使用
Awaited<Type>
可以与 TypeScript 的类型守卫结合使用,以在运行时确定 Promise
的具体类型。
async function fetchUser(): Promise<User | null> {
try {
// 假设这里有异步逻辑来获取用户
return { id: 1, name: "Alice" };
} catch {
return null;
}
}
// 使用类型守卫来处理 null 情况
async function getUserName(): Promise<string> {
const user = await fetchUser();
if (user === null) {
throw new Error("User not found");
}
return user.name;
}
// 使用 Awaited<Type> 获取 getUserName 函数的返回类型
type A = Awaited<ReturnType<typeof getUserName>>;
// A 的类型是 string
Omit<Type,Keys>
用于从一个类型Type
中排除
一组指定的键Keys
,生成一个新的类型。这个工具类型非常有用,特别是在你需要创建一个新类型,这个新类型与原始类型类似,但缺少一些属性时。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
Omit<Type, Keys>
接受两个参数:
Type
:你想要修改的原始类型。Keys
:要排除的属性键的联合类型,可以是字符串字面量、数字字面量或symbol
。
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
type TodoPreview = Omit<Todo, "description" | "createdAt">;
// {
// title: string;
// completed: boolean;
// }
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
Pick<Type,Keys>
用于从一个类型 Type
中挑选
出一组指定的键 Keys
,然后从原始类型中创建一个新的类型,这个新类型只包含这些被挑选的键及其对应的类型。
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
Pick<Type, Keys>
接受两个参数:
Type
:你想要从中选择属性的原始类型。Keys
:要选择的属性键的联合类型,通常是字符串字面量、数字字面量或symbol
类型的值的联合。
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
// {
// title: string;
// completed: boolean;
// }
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
Partical<Type>
用于将一个类型 Type
转换成所有属性都是可选的类型
。这意味着原始类型 Type
中的每个属性,在 Partial<Type>
中都可以不提供值。
type Partial<T> = { [K in keyof T]?: T[K]; };
Partial<Type>
接受一个参数:
Type
:你想要使其属性变为可选的原始类型。
interface Todo {
title: string;
description: string;
}
type PTodo = Partial<Todo>;
// {
// title?: string | undefined;
// description?: string | undefined;
// }
const todo1: PTodo = {
title: "organize desk",
};
练习: 实现Optional
使用Partical<Type>
、Pick<Type,Keys>
、Omit<Type,Keys>
实现Optional
interface Article {
title: string
content: string
date: Date
author: string
}
//当我创建一个文章时,我不想传递date、author,想使用默认值,于是我又需要实现下面的接口
// interface ArticleOptions {
// title: string
// content: string
// date?: Date
// author?: string
// }
//但是这两个接口出现了重复声明,现在我想使用Optional高级类型,实现ArticleOptions接口
//以下是实现
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
type ArticleOptions = Optional<Article, "date" | "author">
ReturnType<Type>
ReturnType<T>
是一个内置的工具类型,用于获取函数类型<T>的返回值类型
。它接受一个函数类型作为参数,并提取出该函数的返回值类型。
下面是 ReturnType
的详细解释:
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any
ReturnType
是一个泛型类型(generic type),使用<T>
来表示。T
表示被检索返回值类型的函数类型。使用extends T
必须是一个函数类型。- 函数类型的签名是
(...args: any[]) => any
,表示这个函数接受任意数量的参数,并且返回任意类型的值。 - 使用条件类型(conditional type)进行检查和提取。
infer R
用于提取函数的返回值类型,并将其赋类型变量。 args: any[]) => infer R ? R : any
表示如果T
是一个函数类型,则返回R
类型,否则返回any
类型。
以下是使用 ReturnType
的示例:
function greet(): string {
return "Hello!"
}
type GreetReturnType = ReturnType<typeof greet> // 类型推断: string
// 在这个示例中,我们使用 greet 函数,并通过 typeof greet 获取其函数类型。然后,我们将 typeof greet 作为参数传递给 ReturnType,以获得函数返回值的类型。
//ReturnType
type greetType = () => string
const greet: greetType = () => {
return ",,,"
}
//这种写法使用 typeof 操作符来获取函数 greet 的类型,获取的就是greetType,也就是下面的第二种写法
type GreetReturnType = ReturnType<typeof greet>
type GreetReturnType = ReturnType<greetType>
⭐ReturnType<typeof greet>
和ReturnType<greet>
是有区别的:
ReturnType<greet>
这种写法直接将 函数greet
作为泛型参数 传递给 ReturnType
,不使用 typeof
操作符。它表明你已知 greet
是一个函数且想要获取其返回值类型,而不关心这个函数的具体类型。
在大多数情况下,它们会得到相同的结果。
通过使用 ReturnType
,我们可以更加灵活地操作和利用函数的返回值类型,例如进行类型检查、定义新的类型等。这个工具类型在处理函数相关的类型操作时非常有用。
Exclude<UnionType, ExcludedMembers>
Exclude<UnionType, ExcludedMembers>
通过从 UnionType
中排除
所有可分配给 ExcludedMembers
的联合成员来构造一个类型。
type Exclude<T, U> = T extends U ? never : T
T
和U
是泛型参数。- 如果
T
是U
的子类型(即可以赋值给U
),那么结果类型为never
。 - 如果
T
不是U
的子类型,那么结果类型为T
。
Exclude<T, U>
的作用是排除类型 T
中可以继承类型 U
的部分,返回剩余的类型。通常会用于条件类型(Conditional Types)中,根据条件排除一些类型。
type A = "a" | "b" | "c"
type B = "a" | "b"
type ExclusiveType = Exclude<A, B> // 'c'
// 由于 B 包含 'a' 和 'b',所以 ExclusiveType 将排除这些共同的部分,剩余的类型为 'c'。
Extract<Type, Union>
通过从 Type
中提取
所有可分配给 Union
的联合成员来构造一个类型。
type Extract<T, U> = T extends U ? T : never;
T extends U ? T : never
是一个条件类型,它使用了 TypeScript 的条件类型守卫语法。- 如果
T
可以赋值给U
(即T
是U
的一个成员),则结果类型是T
;否则,结果类型是never
,表示类型不兼容。
type T0 = Extract<"a" | "b" | "c", "a" | "f">;
// type T0 = "a"
type T1 = Extract<string | number | (() => void), Function>;
// type T1 = ()=>void
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
type T2 = Extract<Shape, { kind: "circle" }>
// type T2 = { kind: "circle"; radius: number }
Record<Keys, Type>
用于创建具有指定属性和对应类型的对象类型。
type Record<K extends keyof any, T> = {
[P in K]: T
}
K
是泛型参数,表示属性名的类型。T
是泛型参数,表示属性值的类型。{ [P in K]: T; }
是通过映射类型将每个属性名K
映射到对应的属性值类型T
。
通过使用 Record
类型工具,我们可以快速创建包含特定属性和对应类型的对象类型。以下是一个示例:
type Person = {
name: string
age: number
}
const myObj: Record<"key1" | "key2" | "key3", Person> = {
key1: { name: "Alice", age: 25 },
key2: { name: "Bob", age: 30 },
key3: { name: "Charlie", age: 35 },
}
//这样的操作使得 myObj 对象具有固定的键集合,并且可以确保每个键对应的属性值类型符合 Person 类型的定义。
//示例二
const layouModules: any = import.meta.glob("../layout/routerView/*.{vue,tsx}")
const viewsModules: any = import.meta.glob("../views/**/*.{vue,tsx}")
const dynamicViewsModules: Record<string, Function> = Object.assign(
{},
{ ...layouModules },
{ ...viewsModules }
)
Readonly<Type>
Readonly<T>
: 创建一个新类型,其属性都变为只读的。
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
todo.title = "Hello";//❌ Cannot assign to 'title' because it is a read-only property.
Required<Type>
Required<T>
: 创建一个新类型,其属性都变为必需的。
interface Props {
a?: number;
b?: string;
}
const obj: Props = { a: 5 };
const obj2: Required<Props> = { a: 5 }; //❌ Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.
Parameters<Type>
从函数类型 Type
的参数
中使用的类型构造元组类型
。
用于获取一个类型参数的详细信息。它通常用于在泛型中获取参数列表,以便在运行时进行类型检查。
declare function f1(arg: { a: number; b: string }): void;
type T0 = Parameters<() => string>;
//type T0 = []
type T1 = Parameters<(s: string) => void>;
//type T1 = [s: string]
type T2 = Parameters<<T>(arg: T) => T>;
//type T2 = [arg: unknown]
type T3 = Parameters<typeof f1>;
//type T3 = [arg: {
//a: number;
//b: string;
//}]
type T4 = Parameters<any>;
//type T4 = unknown[]
type T5 = Parameters<never>;
//type T5 = never
type T6 = Parameters<string>;
//error: Type 'string' does not satisfy the constraint '(...args: any) => any'.
//type T6 = never
type T7 = Parameters<Function>;
//Type 'Function' does not satisfy the constraint '(...args: any) => any'.
//Type 'Function' provides no match for the signature '(...args: any): any'.
//type T7 = never
InstanceType<Type>
用于获取一个构造函数或工厂函数类型 Type
的实例类型。当你有一个类或构造函数,并希望获取该类或构造函数创建的对象的类型时,这个工具类型就非常有用。
InstanceType<Type>
接受一个参数:
Type
:一个构造函数类型,通常是类类型或具有new()
调用签名的函数类型。
type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : never;
T extends new (...args: any[]) => any
表示Type
是一个构造函数。infer R
是一个类型推断,用于获取new (...args: any[]) => R
中R
的类型。- 如果
T
是一个构造函数,InstanceType
将返回构造函数的实例类型;否则,返回never
。
示例:
//类
class User {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
//使用 `InstanceType` 来获取 `User` 类的实例类型:
type UserInstance = InstanceType<typeof User>;
// UserInstance 的类型是 { name: string; age: number; }
// 泛型构造函数
// 当构造函数是泛型的,`InstanceType` 也可以正确工作:
class GenericClass<T> {
value: T;
constructor(value: T) {
this.value = value;
}
}
type GenericInstance = InstanceType<typeof GenericClass>;
// GenericInstance 的类型是 new <T>(value: T) => { value: T; } // GenericClass<unknown>
再比如说vue中,当我们在自定义组件上使用ref
时,给其组件标注类型:
<template>
<NoticeDetail ref="noticeRef" @update-page="getPage"></NoticeDetail>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import NoticeDetail from "@/components/NoticeDetail/index.vue";
defineOptions({ name: "Notice" });
//组件类型标注
const noticeRef = ref<InstanceType<typeof NoticeDetail>>();
</script>
总结
TypeScript的工具类型是构建健壮类型系统的强大工具。它们不仅提高了代码的可读性和可维护性,而且在编译时就能帮助开发者发现潜在的错误。通过合理使用这些工具类型,我们可以编写出更加安全、可靠的应用程序。
随着TypeScript的不断发展,其类型系统也在不断丰富,为开发者提供了更多的类型操作选项。以上列举的并不是全部的工具类型,但是基本上是我们开发过程中可能需要用到的。
❤今天的分享就到这里,希望可以帮助到你!假如你对文章感兴趣,可以来我的公众号:小新学研社。