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的时候,灵活使用其内置的类型方法,会极大的提高我们的开发效率。