类型守卫与高级类型
类型缩小
常见守卫包括 typeof、instanceof、in、判等和自定义类型谓词。
type Success = { status: 'success'; data: string[] };
type Failure = { status: 'failure'; message: string };
type Result = Success | Failure;
function printResult(result: Result) {
if (result.status === 'success') {
console.log(result.data);
} else {
console.error(result.message);
}
}
这种带有公共字面量字段的联合叫可辨识联合,适合请求状态、Reducer Action 和业务状态机。
映射类型
映射类型遍历一组键并生成新对象类型。
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
内置的 Partial、Required、Readonly、Pick、Omit、Record 都建立在这些能力上。
条件类型
type ElementType<T> = T extends readonly (infer U)[] ? U : T;
type A = ElementType<string[]>; // string
type B = ElementType<number>; // number
infer 用于在条件类型中声明待推导类型。
条件类型的分发
当裸类型参数接收联合类型时,条件类型会逐项执行:
type ToArray<T> = T extends unknown ? T[] : never;
type Value = ToArray<string | number>; // string[] | number[]
用元组包裹两侧可以阻止分发:
type ToArrayNonDist<T> = [T] extends [unknown] ? T[] : never;
type Value = ToArrayNonDist<string | number>; // (string | number)[]
函数重载与联合类型
当不同输入对应不同输出时可以使用重载:
function normalize(value: string): string[];
function normalize(value: string[]): string[];
function normalize(value: string | string[]): string[] {
return Array.isArray(value) ? value : [value];
}
如果输入与输出没有这种对应关系,优先使用联合类型,避免制造过多重载。
高频工具类型手写
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type MyExclude<T, U> = T extends U ? never : T;
type MyReturnType<T extends (...args: never[]) => unknown> =
T extends (...args: never[]) => infer R ? R : never;
面试时不仅要写出结果,还要解释 keyof、映射遍历、条件分发和 infer 各自承担的作用。