阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 条件类型应用

条件类型应用

作者:陈川 阅读数:39301人阅读 分类: TypeScript

条件类型基础概念

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提供了一些内置的条件类型工具,它们本身就是用条件类型实现的:

  1. Exclude<T, U> - 从T中排除可以赋值给U的类型
type T = Exclude<"a" | "b" | "c", "a">;  // "b" | "c"
  1. Extract<T, U> - 从T中提取可以赋值给U的类型
type T = Extract<"a" | "b" | 1 | 2, string>;  // "a" | "b"
  1. 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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌