引言
TypeScript,作为JavaScript的超集,通过引入静态类型系统,旨在提高代码的可读性和可维护性。其类型系统的强大之处不仅在于能够准确地注解和推断变量的类型,还在于它提供了一系列高级工具类型,使得开发者可以在类型层面进行编程,执行所谓的"类型体操"。
基础动作
Partial
Partial<Type>
是TypeScript提供的一个工具类型,它将某个类型里的所有属性变为可选的。这在处理不完全对象时非常有用。比如,在函数参数或者设置默认值时,我们不需要提供所有属性。
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>): Todo {
return { ...todo, ...fieldsToUpdate };
}
Required
与Partial
相反,Required<Type>
将类型的所有属性从可选转为必选。这对确保对象满足特定结构特别有用,尤其是在处理不允许省略任何属性的场景下。
typescriptCopy code
interface Props {
a?: number;
b?: string;
}
// 下面的代码会报错,因为属性'b'是必须的
const obj: Required<Props> = { a: 5 };
Readonly
Readonly<Type>
将类型的所有属性设置为只读,这意味着一旦对象被创建,其属性就不能被修改。这对于创建不可变数据非常有用。
typescriptCopy code
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
// 下面的代码会报错,因为'title'属性是只读的
todo.title = "Hello";
Pick<Type, Keys>
Pick<Type, Keys>
从类型中选取一组属性来构造一个新类型。这对于限制对象只包含某些属性非常有用,可以看作是对类型进行裁剪。
typescriptCopy code
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
Omit<Type, Keys>
Omit<Type, Keys>
与Pick
相反,它从类型中排除一组属性。这在需要从对象类型中删除某些属性时非常有用。
typescriptCopy code
interface Todo {
title: string;
description: string;
completed: boolean;
}
// 移除'description'属性
type TodoPreview = Omit<Todo, "description">;
高级挑战
让我们通过一些具体的例子来深入理解前面提到的几个TypeScript类型挑战:
可串联构造器(Chainable)
typescriptCopy code
type Chainable<T = {}> = {
option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<T & Record<K, V>>;
get(): T;
};
// 使用例子
const config: Chainable = {};
const result = config
.option('foo', 123)
.option('name', 'TypeScript')
.option('bar', { value: 'Hello World' })
.get();
// result 的类型为:
// {
// foo: number;
// name: string;
// bar: { value: string; }
// }
在这个例子中,Chainable
类型使用泛型T
来累积之前调用option
方法时传入的键值对。每次调用option
时,都会返回一个新的Chainable
类型,这个类型中包含了所有之前添加的键值对。
元组转合集(TupleToUnion)
typescriptCopy code
type TupleToUnion<T> = T extends (infer U)[] ? U : never;
// 使用例子
type TestTuple = ['1', '2', '3'];
type Result = TupleToUnion<TestTuple>; // '1' | '2' | '3'
这里,TupleToUnion
类型通过条件类型和infer
关键字来推断元组中的元素类型,并将其转换为一个联合类型。
最后一个元素(Last)
typescriptCopy code
type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never;
// 使用例子
type TestArray = [1, 2, 3, 4];
type Result = Last<TestArray>; // 4
在这个例子中,Last
类型使用条件类型和剩余参数语法来匹配元组的最后一个元素,并推断出其类型。
出堆(Pop)
typescriptCopy code
type Pop<T extends any[]> = T extends [...infer Rest, infer _] ? Rest : never;
// 使用例子
type TestArray = [1, 2, 3, 4];
type Result = Pop<TestArray>; // [1, 2, 3]
这里的Pop
类型利用了和Last
类似的技巧,但是它返回的是除了最后一个元素之外的所有元素组成的数组类型。
Promise.all的类型实现(PromiseAll)
typescriptCopy code
declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<{ [K in keyof T]: T[K] extends Promise<infer R> ? R : T[K] }>;
// 使用例子
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve) => resolve('Hello World'));
const result = PromiseAll([promise1, promise2, promise3]);
// result 的类型为 Promise<[number, number, string]>
PromiseAll
类型实现模拟了Promise.all
的行为,通过条件类型和infer
关键字来推断并处理数组中每个Promise的解析结果类型。