条件类型与分布式条件类型
TypeScript 的条件类型和分布式条件类型是类型系统中的高级特性,能够基于输入类型动态推导出更复杂的类型关系。它们通过类型层面的条件分支和自动分发机制,显著增强了类型编程的灵活性。
条件类型基础
条件类型的语法类似于三元表达式,形式为 T extends U ? X : Y
。当类型 T
可赋值给类型 U
时,返回类型 X
,否则返回类型 Y
。这种机制允许在类型层面实现条件判断:
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false
条件类型常与泛型结合使用,创建动态的类型推导。例如实现一个提取数组元素类型的工具类型:
type ElementType<T> = T extends (infer U)[] ? U : never;
type Numbers = ElementType<number[]>; // number
type Mixed = ElementType<(string | boolean)[]>; // string | boolean
分布式条件类型
当条件类型作用于联合类型时,会发生分布式行为。这是 TypeScript 的类型系统中最强大的特性之一。具体表现为:对于 T extends U ? X : Y
,如果 T
是联合类型 A | B | C
,则结果会分发为 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
。
type ToArray<T> = T extends any ? T[] : never;
type Numbers = ToArray<number>; // number[]
type UnionArray = ToArray<number | string>; // number[] | string[]
分布式特性在过滤联合类型时特别有用。例如实现一个从联合类型中排除特定类型的工具:
type Exclude<T, U> = T extends U ? never : T;
type WithoutNumbers = Exclude<string | number | boolean, number>; // string | boolean
条件类型中的类型推断
infer
关键字允许在条件类型中声明临时类型变量,用于捕获深层类型信息。这在解构复杂类型时非常有用:
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type StringResult = UnwrapPromise<Promise<string>>; // string
type NumberResult = UnwrapPromise<number>; // number
多层嵌套的 infer
可以处理更复杂的场景。例如提取函数返回的 Promise 的解析类型:
type UnwrapPromiseReturn<T> =
T extends (...args: any[]) => Promise<infer U> ? U :
T extends (...args: any[]) => infer V ? V :
never;
type A = UnwrapPromiseReturn<() => Promise<number>>; // number
type B = UnwrapPromiseReturn<() => string>; // string
禁用分布式行为
有时需要禁用分布式行为,可以通过将检查的类型包装在元组中实现:
type NonDistributive<T> = [T] extends [any] ? true : false;
// 非分布式行为
type Test1 = NonDistributive<string | number>; // true
// 对比分布式行为
type Distributive<T> = T extends any ? true : false;
type Test2 = Distributive<string | number>; // true | true
实际应用示例
条件类型在 React 属性类型处理中很实用。例如创建一个根据组件 props
动态决定 children
类型的工具:
type PropsWithChildren<P> =
'children' extends keyof P
? P
: P & { children?: React.ReactNode };
function createComponent<P>(props: PropsWithChildren<P>) {
// 组件实现
}
// 自动推断 children 类型
createComponent({ value: 1 }); // children 可选
createComponent({ children: <div/>, value: 1 }); // children 已存在
另一个典型应用是处理 Redux action 类型:
type Action<T extends string, P = void> = {
type: T;
payload: P;
};
type ExtractAction<A, T> = A extends Action<infer U, infer P>
? U extends T
? P
: never
: never;
type MyActions =
| Action<'ADD', number>
| Action<'REMOVE', string>;
type AddPayload = ExtractAction<MyActions, 'ADD'>; // number
type RemovePayload = ExtractAction<MyActions, 'REMOVE'>; // string
递归条件类型
TypeScript 4.1 引入了递归条件类型,允许类型自引用。这使得处理嵌套结构成为可能:
type DeepReadonly<T> =
T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
type NestedData = {
a: number;
b: {
c: string;
d: boolean[];
};
};
type ReadonlyData = DeepReadonly<NestedData>;
/* 等价于:
{
readonly a: number;
readonly b: {
readonly c: string;
readonly d: readonly boolean[];
};
}
*/
递归条件类型还能实现类型层面的数组操作,比如反转元组:
type Reverse<T extends any[]> =
T extends [infer First, ...infer Rest]
? [...Reverse<Rest>, First]
: [];
type Numbers = [1, 2, 3, 4];
type Reversed = Reverse<Numbers>; // [4, 3, 2, 1]
条件类型与模板字面量类型
结合 TypeScript 4.1 的模板字面量类型,条件类型可以实现更强大的字符串操作:
type ExtractRouteParams<T> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractRouteParams<Rest>]: string }
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
type Params = ExtractRouteParams<'/user/:id/posts/:postId'>;
/* 类型为:
{
id: string;
postId: string;
}
*/
性能考量
复杂条件类型可能导致类型检查变慢,特别是在递归深度较大时。可以通过以下方式优化:
- 避免过深的递归层级
- 使用尾递归优化(TypeScript 4.5+)
- 对大型联合类型提前进行过滤
// 尾递归优化示例
type TrimLeft<T extends string> =
T extends ` ${infer Rest}` ? TrimLeft<Rest> : T;
type Trimmed = TrimLeft<' hello'>; // "hello"
条件类型与映射类型结合
结合映射类型可以创建更灵活的类型转换工具。例如实现一个将接口所有方法返回类型包装为 Promise 的工具:
type Promisify<T> = {
[K in keyof T]: T[K] extends (...args: infer A) => infer R
? (...args: A) => Promise<R>
: T[K];
};
interface Api {
getUser(id: number): { name: string };
checkStatus(): boolean;
}
type AsyncApi = Promisify<Api>;
/* 等价于:
{
getUser(id: number): Promise<{ name: string }>;
checkStatus(): Promise<boolean>;
}
*/
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn