条件类型应用
条件类型基础概念
TypeScript中的条件类型允许我们根据输入类型的关系选择不同的类型。它采用T extends U ? X : Y
的形式,类似于JavaScript中的三元运算符。当类型T
可分配给类型U
时,结果类型为X
,否则为Y
。
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false
条件类型在泛型中特别有用,可以根据类型参数动态决定结果类型。它们经常与infer
关键字结合使用,从复杂类型中提取部分类型。
分布式条件类型
当条件类型作用于联合类型时,会发生"分布式"行为。这意味着条件类型会分布在联合类型的每个成员上。
type ToArray<T> = T extends any ? T[] : never;
type StrArrOrNumArr = ToArray<string | number>;
// 等同于 string[] | number[]
这种特性使得我们可以方便地处理联合类型。如果不希望发生分布式行为,可以用方括号将extends
两边包裹起来:
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Arr = ToArrayNonDist<string | number>;
// 等同于 (string | number)[]
类型推断与infer关键字
infer
关键字允许我们在条件类型中声明一个类型变量,用于捕获要推断的类型。这在提取复杂类型的部分时非常有用。
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
function foo() { return 42; }
type FooReturn = ReturnType<typeof foo>; // number
infer
可以用于各种类型位置,包括函数参数、数组元素、Promise的解析值等:
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type Num = UnpackPromise<Promise<number>>; // number
内置实用条件类型
TypeScript提供了一些内置的条件类型工具,它们本身就是用条件类型实现的:
Exclude<T, U>
- 从T中排除可以赋值给U的类型
type T = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
Extract<T, U>
- 从T中提取可以赋值给U的类型
type T = Extract<"a" | "b" | 1 | 2, string>; // "a" | "b"
NonNullable<T>
- 从T中排除null和undefined
type T = NonNullable<string | null | undefined>; // string
递归条件类型
条件类型可以递归引用自身,用于处理嵌套结构或递归类型转换:
type DeepReadonly<T> =
T extends object ? { readonly [P in keyof T]: DeepReadonly<T[P]> } : T;
type Nested = {
a: number;
b: {
c: boolean;
d: {
e: string;
};
};
};
type ReadonlyNested = DeepReadonly<Nested>;
递归条件类型在处理深度嵌套的数据结构时非常强大,但需要注意递归深度限制。
条件类型与映射类型结合
条件类型可以与映射类型结合使用,创建更灵活的类型转换:
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T];
type Person = {
name: string;
age: number;
greet: () => void;
};
type PersonMethods = FunctionPropertyNames<Person>; // "greet"
这种组合可以筛选出特定类型的属性,或者对不同类型的属性应用不同的转换。
条件类型中的类型约束
在条件类型中,我们可以对类型参数添加额外的约束:
type Flatten<T> = T extends Array<infer U> ? U : T;
type StringArray = Array<string>;
type StringItem = Flatten<StringArray>; // string
当处理可能为数组也可能不是数组的类型时,这种模式特别有用。
条件类型与模板字面量类型
TypeScript 4.1引入的模板字面量类型可以与条件类型结合:
type GetterName<T extends string> = `get${Capitalize<T>}`;
type NameGetter = GetterName<'name'>; // "getName"
type PropType<T, K extends string> =
K extends `on${infer Event}` ? (e: Event) => void : T[K];
type Props = {
onClick: (e: MouseEvent) => void;
value: string;
};
type ClickHandler = PropType<Props, 'onClick'>; // (e: MouseEvent) => void
这种组合在处理事件处理程序或特定命名约定的API时特别有用。
条件类型在复杂类型操作中的应用
条件类型可以用于构建复杂的类型操作,如类型安全的Redux reducer:
type Action<T extends string = string, P = any> = {
type: T;
payload: P;
};
type ActionCreator<A extends Action> = (
payload: A['payload']
) => A;
type Reducer<S = any, A extends Action = Action> = (
state: S,
action: A
) => S;
type Handlers<S, A extends Action> = {
[T in A['type']]: Reducer<S, Extract<A, { type: T }>>
};
function createReducer<S, A extends Action>(
initialState: S,
handlers: Handlers<S, A>
): Reducer<S, A> {
return (state = initialState, action) => {
const handler = handlers[action.type];
return handler ? handler(state, action) : state;
};
}
这个例子展示了如何用条件类型创建类型安全的Redux reducer,确保action类型与对应的handler匹配。
条件类型与函数重载
条件类型可以简化函数重载的类型定义:
type Overloads = {
(x: string): number;
(x: number): string;
};
type ReturnTypeByParam<T> =
T extends string ? number :
T extends number ? string :
never;
function overloaded<T extends string | number>(x: T): ReturnTypeByParam<T>;
function overloaded(x: any): any {
return typeof x === 'string' ? x.length : x.toString();
}
const a = overloaded('hello'); // number
const b = overloaded(42); // string
这种方法比传统的函数重载更简洁,特别是当重载数量较多时。
条件类型与递归类型限制
TypeScript对递归类型深度有限制,当处理深度递归时可能需要特殊技巧:
type MaxDepth = 5; // 防止无限递归
type DeepPartial<T, Depth extends number = 0> =
Depth extends MaxDepth ? T :
T extends object ? {
[P in keyof T]?: DeepPartial<T[P], AddOne<Depth>>;
} : T;
type AddOne<N extends number> =
[1, 2, 3, 4, 5, 6][N]; // 简单的数字递增
interface ComplexObject {
a: {
b: {
c: {
d: {
e: string;
};
};
};
};
}
type PartialComplex = DeepPartial<ComplexObject>;
这个例子展示了如何限制递归深度,防止类型检查器陷入无限递归。
条件类型与类型谓词
条件类型可以与类型谓词结合,创建更精确的类型守卫:
type IsArray<T> = T extends any[] ? true : false;
function filterArray<T>(items: T[] | T): IsArray<T> extends true ? T[] : T {
return Array.isArray(items) ? items : [items] as any;
}
const arr = filterArray([1, 2, 3]); // number[]
const single = filterArray(42); // number
虽然需要类型断言,但这种模式可以在复杂场景下提供更好的类型安全性。
条件类型与可变元组类型
TypeScript 4.0引入的可变元组类型可以与条件类型结合:
type Shift<T extends any[]> =
T extends [infer First, ...infer Rest] ? Rest : [];
type Tuple = [string, number, boolean];
type Rest = Shift<Tuple>; // [number, boolean]
type Last<T extends any[]> =
T extends [...infer _, infer Last] ? Last : never;
type LastItem = Last<Tuple>; // boolean
这些工具类型在处理元组时非常有用,特别是与函数参数列表相关的情况。
条件类型与品牌类型
条件类型可以用于创建和处理品牌类型(Branded Types):
type Brand<T, B> = T & { __brand: B };
type UserId = Brand<string, 'UserId'>;
type PostId = Brand<string, 'PostId'>;
type IsBrand<T, B> = T extends Brand<infer U, B> ? true : false;
type A = IsBrand<UserId, 'UserId'>; // true
type B = IsBrand<PostId, 'UserId'>; // false
品牌类型可以帮助区分语义上不同但结构相同的类型,条件类型则可以检查和处理这些品牌类型。
条件类型与类型级编程
条件类型可以实现复杂的类型级编程,如类型算术:
type Length<T extends any[]> =
T extends { length: infer L } ? L : never;
type Tuple = [string, number, boolean];
type Len = Length<Tuple>; // 3
type BuildTuple<L extends number, T extends any[] = []> =
T['length'] extends L ? T : BuildTuple<L, [...T, any]>;
type Tuple3 = BuildTuple<3>; // [any, any, any]
虽然TypeScript的类型系统不是图灵完备的,但条件类型允许我们实现许多有用的类型级计算。
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:自定义工具类型
下一篇:infer关键字与类型提取