TypeScript在最近一直饱受争议,但是在我看来,作为规范、约束JS的超集,他的优点还是很多的,在多人协调项目上用TS,在规范类型,语法提示上实在不要太友好!所以接下来我为大家介绍它本身的内置工具类型,提升我们类型定义的速度与可读性。
Partial<T>
- 作用:将类型T的所有属性设置为可选。
- 例子:
typescript
interface User {
id: number;
name: string;
email: string;
}
const updateUser: Partial<User> = {}

在这个例子中,我们创建了一个updateUser对象,它只包含了User接口中的name属性,而id和email属性则是可选的。
在实战中,我们对于一些模型参数的要求是必要的,一些变量的定义却要求不是必要的,就可以使用它(不同业务上,数据类型相同)
- 源码解析:查看内置源码
ini
type Partial<T> = {
[P in keyof T]?: T[P];
};
- 使用映射类型,我们可以为类型
T中的每个属性P定义一个新的类型。keyof T获取T中所有属性的名称组成的联合类型。 - 通过
?,我们告诉 TypeScript 将T中属性P变为可选的。这意味着无论T中的属性P是否原本是可选的,在Partial<T>类型中,它都将是可选的。 - 这里,我们使用
T[P]来表示属性P的原始类型。因此,我们不仅仅添加了可选性,还保留了属性的原始类型。
Required<T>
- 作用:与Partial相反,Required工具类型将类型T的所有属性变为必填的。
- 例子:
ini
interface User {
id?: number;
name?: string;
email?: string;
}
type RequiredPerson = Required<User>
const updateUser: RequiredPerson = {}
在上面的例子中,User 类型中的 name 和 id 等属性都是可选的。但是,当我们使用 Required<User> 得到 RequiredPerson 类型时,这几个属性都变为了必需的。
- 源码解析:查看内置源码
ini
type Required<T> = {
[P in keyof T]-?: T[P];
};
- 定义了一个泛型类型
Required,它接受一个类型参数T。 - 使用映射类型,我们可以为类型
T中的每个属性P定义一个新的类型。keyof T会获取T中所有属性的名称组成的联合类型。 - 通过
-?,我们告诉 TypeScript 移除T中属性P的可选性。这意味着如果T中的属性P是可选的,那么在Required<T>类型中,它将是必需的。 - 这里,我们使用
T[P]来表示属性P的原始类型。因此,我们不仅移除了可选性,还保留了属性的原始类型。
Readonly<T>
- 作用:将类型T的所有属性变为只读属性,这意味着这些属性不能被重新赋值。
- 例子:
ini
type Person = {
name: string;
age: number;
};
type ReadonlyPerson = Readonly<Person>;
const person: ReadonlyPerson = {
name: "Alice",
age: 30
};
// 下面的代码会报错,因为 name 属性是只读的
// person.name = "Bob";

在上面的示例中,person 是一个 ReadonlyPerson 类型的对象,其 name 属性是只读的。因此,尝试修改 person.name 的值会导致 TypeScript 编译器报错。
在实战中,我们某些特定的属性不允许被修改,例如一些影响着全局的常量。
- 源码解析:查看内置源码
ini
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
- 定义了一个泛型类型
Readonly,它接受一个类型参数T - 这里使用了映射类型的语法,它遍历类型
T的所有属性键,并为每个属性键P定义一个只读属性。in keyof T表示我们正在遍历T的所有键。 - 对于
T中的每个属性键P,我们使用T[P]来获取T类型中对应属性的类型。通过前缀readonly,我们告诉 TypeScript 该属性是只读的,即不能被重新赋值。
Pick<T, K>
- 作用:从类型T中选择一些属性K来形成新的类型。这允许你从一个更大的类型中"挑选"出你需要的属性。
- 例子:
ini
interface Todo {
id: number;
title: string;
completed: boolean;
description: string;
}
type TodoPreview = Pick<Todo, 'id' | 'title'>;
const todoPreview: TodoPreview = {
id: 1,
title: 'Buy milk'
};

在这个例子中,我们只从Todo接口中选择了id和title属性来创建一个新的TodoPreview类型,用于显示预览信息。
这个方法就很实用,很多时候我们字段相同的情况下都是用联合类型重复定义处理的,这个方法完全可以抽离类型复用。
- 源码解析:查看内置源码
scala
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
- 定义了一个泛型类型
Pick,它接受两个类型参数:T和K。K是一个扩展自keyof T的类型,意味着K必须是T的属性键的集合。 - 使用映射类型,我们为类型
K中的每个属性P定义了一个新的类型。in K表示我们正在遍历K中的每个属性。 - 对于
K中的每个属性P,我们使用T[P]来获取T类型中对应属性的类型。这确保了新创建的类型会保留原类型T中对应属性的类型。
Exclude<T, U>
- 作用:从类型T中排除那些可以赋值给类型U的类型。
- 例子:
ini
type Animals = 'Dog' | 'Cat' | 'Fish';
type LandAnimals = Exclude<Animals, 'Fish'>; // 'Dog' | 'Cat'
在这个例子中,我们使用Exclude从Animals类型中排除特定的值。
这个方法用法比较广,例如,一个联合枚举中,我们只需要特定的某几个状态,就可以排除不需要的值
- 源码解析:查看内置源码
r
type Exclude<T, U> = T extends U ? never : T;
- 定义了一个泛型类型
Exclude,它接受两个类型参数:T和U。 - 条件类型
- 如果
T是U的子类型(T extends U为true),则结果是never类型。never是 TypeScript 中的一个特殊类型,表示一个永远不会有值的类型。在类型操作中,never类型可以用作一个"过滤器",因为它不会出现在任何有效的值的类型中。 - 如果
T不是U的子类型(T extends U为false),则结果是T本身。
- 如果
- 由于条件类型的作用,
Exclude<T, U>将返回类型T中所有不能赋值给类型U的子类型。换句话说,它排除了T中所有与U重叠的部分。
Extract<T, U>
- 作用:类型T中提取那些可以赋值给类型U的类型
- 例子:
ini
type Animals = 'Dog' | 'Cat' | 'Fish';
// type LandAnimals = Exclude<Animals, 'Fish'>; // 'Dog' | 'Cat'
type AquaticAnimals = Extract<Animals, 'Fish' | 'Crocodile'>; // 'Fish'

在这个例子中,我们使用Extract从Animals类型中提取特定的值。
这个实战中用的比较少,一般用作获取两个枚举间的相同属性值
- 源码解析:查看内置源码
r
type Extract<T, U> = T extends U ? T : never;
跟Exclude相反,就不多做解释。
Omit<T, K>
- 作用: 从类型T中排除一些属性K来形成新的类型。这可以用来创建一个不包含某些属性的新类型。
- 例子:
ini
type Person = {
id: number;
name: string;
age: number;
address: string;
};
type PersonWithoutAddress = Omit<Person, 'address'>;
const person: PersonWithoutAddress = {
id: 1,
name: 'John Doe',
age: 30,
number: 10
};

这里,我们创建了一个PersonWithoutAddress类型,它排除了Person类型中的address属性并重写了address类型为number。
实战中也比较常用到,一般都是联合重写或者剔除某个属性。
- 源码解析:查看内置源码
scala
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
比较复杂,联合了两种内置方法,接下来一步步解析
- 定义了一个泛型类型
Omit,它接受两个类型参数:T和K。K被定义为扩展自keyof any的类型,这基本上意味着K可以是任何类型的属性键的集合。但是,在实际使用中,K通常会是keyof T的子集,即T的某些属性键的集合。 - 在这里,我们使用
Exclude<keyof T, K>来获取T中除了K以外的所有属性键。 - 之前我们解析过
Pick类型,它用于从类型T中选取一部分属性来创建一个新的类型。在这里,我们结合Exclude的结果,使用Pick来选取T中除了K以外的所有属性。
不理解没关系,多看几遍就行了
Record<K, T>
- 作用:用一组类型为K的keys来构造一个对象类型,其值的类型为T。
- 例子:
ini
type FeatureFlags = Record<string, boolean>;
const flags: FeatureFlags = {
isFeatureAEnabled: true,
isFeatureBEnabled: false
};

这里,我们定义了一个FeatureFlags类型,它允许我们以字符串为键,布尔值为值来创建对象。
实际业务上,我们一般用来列举不确定性的Object,省了key-string:value-统一类型的定义。
- 源码解析:查看内置源码
scala
type Record<K extends keyof any, T> = {
[P in K]: T;
};
- 这里定义了一个泛型类型
Record,它接受两个类型参数:K和T。K必须是某个对象类型的键的子集,这是通过extends keyof any来约束的。keyof any表示任何对象类型的键的集合,因此K可以是任何对象类型的键的任意子集。 - 同样地,这里使用了映射类型的语法,它遍历类型
K的所有属性键,并为每个属性键P定义一个类型为T的属性。 - 对于
K中的每个属性键P,我们定义了一个类型为T的属性。这样,我们就构造了一个对象类型,该对象的键是K的子集,并且所有键的值都是T类型。
ReturnType<T>
- 作用:获取函数类型T的返回类型。
- 例子:
javascript
function handler() {
return {
get() {},
set() {},
remove() {},
delete() {}
}
}
export type Handler = ReturnType<typeof handler>
在这个例子中,我们定义了一个函数 handler,它返回一个对象里包含的几个方法。然后,我们使用 ReturnType 和 typeof 操作符来获取这个函数的返回类型,并将结果存储在 Handler 类型中。结果是一个类型,表示 handler 的返回类型。
这个实战中也是比较常用,一般用作描述一个工具函数的返回类型。
- 源码解析:查看内置源码
注意:用到了三元条件类型判断
- 定义了一个泛型类型
ReturnType,它接受一个类型参数T。T必须是一个函数类型,该函数接受任意数量和类型的参数,并返回任意类型的结果。 - 条件类型允许我们根据条件选择两种类型之一,在源码中,我们检查
T是否扩展(即兼容)于一个函数类型,该函数接受任意参数并返回一个可以推断的类型R。- 如果
T是一个这样的函数类型,那么infer R会推断出函数的返回类型,并且条件类型的结果就是R。 - 如果
T不是这样的函数类型(或者不能推断出返回类型),那么条件类型的结果就是any。
- 如果
infer关键字用于在条件类型中创建一个新的类型变量。在这里,R是推断出的函数返回类型。TypeScript 会尝试从T中推断出R的具体类型。
在日常业务使用TS的时候,灵活使用其内置的类型方法,会极大的提高我们的开发效率。