TypeScript泛型高级指南:条件类型、映射类型与类型体操实战

TypeScript泛型核心机制

TypeScript的类型系统图灵完备,泛型是构建可复用类型逻辑的基石。泛型允许在定义函数、接口、类型别名时留出类型参数位,在实际使用时填入具体类型。这和函数参数留出值位、调用时填入具体值是同一个思路,区别在于泛型参数在类型层面运作,编译后完全擦除。

// 基础泛型函数:类型参数T在调用时确定
function identity<T>(value: T): T {
    return value;
}

const num = identity(42);        // T推导为number
const str = identity("hello");    // T推导为string
const explicit = identity<boolean>(true);  // 显式指定

泛型约束(Constraint)限制类型参数必须满足的结构:

// T必须具有length属性
function logLength<T extends { length: number }>(value: T): number {
    return value.length;
}

logLength("abc");       // OK, string有length
logLength([1, 2, 3]);  // OK, Array有length
logLength(123);        // Error: number没有length

// keyof约束:K必须是T的属性键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name");  // 推导为string
const age = getProperty(user, "age");     // 推导为number

条件类型与类型推断

条件类型是TypeScript类型体操的核心工具,语法 T extends U ? X : Y 在类型层面做条件判断:

// 实现ReturnType:提取函数返回值类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type Fn = (x: number) => string;
type Result = MyReturnType<Fn>;  // string

// 实现Parameters:提取函数参数类型为元组
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;

type Params = MyParameters<(a: string, b: number) => void>;  // [string, number]

// infer关键字在条件类型的extends子句中声明待推断的类型变量
// 只能在条件类型的真分支中访问infer推断的结果

分布式条件类型:当类型参数是联合类型时,条件类型会自动分发,逐一判断:

type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;  // string[] | number[]
// 不是 (string | number)[]

// 阻止分发:用元组包裹
type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNoDistribute<string | number>;  // (string | number)[]

内置工具类型 ExcludeExtractNonNullable 都基于分布式条件类型实现:

type MyExclude<T, U> = T extends U ? never : T;
type MyExtract<T, U> = T extends U ? T : never;
type MyNonNullable<T> = T extends null | undefined ? never : T;

type T1 = MyExclude<"a" | "b" | "c", "a">;       // "b" | "c"
type T2 = MyExtract<string | number | boolean, number>;  // number
type T3 = MyNonNullable<string | null | undefined>;     // string

映射类型与模板字面量类型

映射类型(Mapped Types)遍历联合类型中的每个键,生成新的对象类型:

// 实现Partial:将所有属性变为可选
type MyPartial<T> = {
    [K in keyof T]?: T[K];
};

// 实现Required:将所有属性变为必选
type MyRequired<T> = {
    [K in keyof T]-?: T[K];  // -?移除可选修饰符
};

// 实现Readonly:将所有属性变为只读
type MyReadonly<T> = {
    readonly [K in keyof T]: T[K];
};

// 实现Mutable:移除readonly
type Mutable<T> = {
    -readonly [K in keyof T]: T[K];
};

模板字面量类型(Template Literal Types)在类型层面拼接字符串,配合映射类型可以生成大量类型安全的代码:

// 事件监听器的类型安全
type EventName = "click" | "focus" | "blur";
type EventHandler<T extends EventName> = `on${Capitalize<T>}`;
// "onClick" | "onFocus" | "onBlur"

// CSS属性的类型安全
type CSSProperty = "margin" | "padding" | "border";
type CSSDirection = "top" | "right" | "bottom" | "left";
type CSSRule = `${CSSProperty}-${CSSDirection}`;
// "margin-top" | "margin-right" | ... | "border-left"

// 实现类型安全的事件系统
type EventMap = {
    click: { x: number; y: number };
    keypress: { key: string; code: string };
    resize: { width: number; height: number };
};

function on<K extends keyof EventMap>(
    event: K,
    handler: (payload: EventMap[K]) => void
): void {
    // 实现
}

on("click", (payload) => {
    payload.x;  // 类型推导为number
    payload.y;  // 类型推导为number
});

高级类型模式实战

递归类型:实现深层Readonly:

type DeepReadonly<T> = {
    readonly [K in keyof T]: T[K] extends object
        ? T[K] extends Function
            ? T[K]           // 函数不递归
            : DeepReadonly<T[K]>  // 对象递归
        : T[K];              // 基本类型直接返回
};

interface Config {
    db: { host: string; port: number; options: { ssl: boolean } };
    cache: { ttl: number };
}
type FrozenConfig = DeepReadonly<Config>;
// db.port和db.options.ssl都变为readonly

类型推导与函数重载的结合:实现类型安全的事件发射器:

type EventPayloads = {
    userLogin: { userId: string; timestamp: number };
    pageView: { url: string; referrer: string };
    error: { message: string; stack?: string };
};

class TypedEventEmitter<Events extends Record<string, any>> {
    private listeners = new Map<keyof Events, Set<Function>>();

    on<K extends keyof Events>(
        event: K,
        handler: (payload: Events[K]) => void
    ): () => void {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, new Set());
        }
        this.listeners.get(event)!.add(handler);
        // 返回取消订阅函数
        return () => this.listeners.get(event)?.delete(handler);
    }

    emit<K extends keyof Events>(event: K, payload: Events[K]): void {
        this.listeners.get(event)?.forEach(fn => fn(payload));
    }
}

const emitter = new TypedEventEmitter<EventPayloads>();
emitter.on("userLogin", (p) => console.log(p.userId));  // p推导正确
emitter.emit("pageView", { url: "/", referrer: "" });    // payload类型检查
emitter.emit("error", { message: "fail" });               // stack可选

类型体操经典题解析

实现DeepPartial:递归将所有嵌套属性变为可选:

type DeepPartial<T> = T extends Function
    ? T
    : T extends object
    ? { [K in keyof T]?: DeepPartial<T[K]> }
    : T;

// 验证
type Nested = { a: { b: { c: string } }; d: number };
type PartialNested = DeepPartial<Nested>;
// { a?: { b?: { c?: string } }; d?: number }

实现PickByType:按值类型筛选属性:

type PickByType<T, ValueType> = {
    [K in keyof T as T[K] extends ValueType ? K : never]: T[K]
};

interface Data {
    name: string;
    age: number;
    active: boolean;
    score: number;
}

type NumberFields = PickByType<Data, number>;
// { age: number; score: number }

type StringFields = PickByType<Data, string>;
// { name: string }

实现TupleToUnion:元组转联合类型:

type TupleToUnion<T extends readonly unknown[]> = T[number];

type Colors = readonly ["red", "green", "blue"];
type Color = TupleToUnion<Colors>;  // "red" | "green" | "blue"

TypeScript泛型的能力远超”给函数加类型参数”的范畴。条件类型、映射类型、模板字面量类型、infer推断组合起来,可以在类型层面实现复杂的逻辑运算,在编译期捕获大量运行时错误。类型体操的目标不是写出最复杂的类型表达式,而是用最精确的类型约束来描述业务逻辑,让编译器成为最可靠的代码审查工具。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-fan-xing-gao-ji-zhi-nan-tiao-jian-lei-xing-ying/

(0)
小编小编
上一篇 1天前
下一篇 1天前

相关推荐