TypeScript类型体操实战:泛型约束与条件类型高级用法详解

TypeScript实战中,泛型和条件类型是类型系统最强大的特性,也是前端工程化中类型安全的核心保障。组件库设计中,合理的泛型约束能让组件API既灵活又类型安全,避免any泛滥。本文从实际场景出发,演示7种高频使用的类型编程模式,每种都附带可运行的代码示例。

泛型约束:限制类型参数的范围

未约束的泛型只能访问Object类型的方法。通过extends关键字约束泛型,可以要求类型参数具备特定属性。

// 基础泛型:T可以是任何类型
function getProperty<T>(obj: T, key: string) {
    return obj[key]; // 报错:T上不存在索引签名
}

// 泛型约束:要求T有length属性
interface HasLength {
    length: number;
}

function logLength<T extends HasLength>(value: T): T {
    console.log(value.length);
    return value;
}

logLength("hello");     // string有length属性,通过
logLength([1, 2, 3]);   // array有length属性,通过
logLength(123);          // number没有length属性,编译报错

keyof操作符:提取类型的键集合

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user = {
    name: "张三",
    age: 28,
    email: "zhangsan@example.com"
};

// K被推断为"name" | "age" | "email"
// 返回类型自动推断为string | number
const name = getProperty(user, "name");   // 类型: string
const age = getProperty(user, "age");     // 类型: number
const invalid = getProperty(user, "phone"); // 编译报错: "phone"不在keyof user中

Vue3生态中,这个模式常用于组件Props的类型推导,确保传入的属性名合法且类型正确。

条件类型:类型层面的if-else

// 基本语法: T extends U ? X : Y
type IsString<T> = T extends string ? true : false;

type A = IsString<string>;    // true
type B = IsString<number>;    // false
type C = IsString<"hello">;   // true (字面量类型也是string的子类型)

// 条件类型的分布特性
type ExcludeString<T> = T extends string ? never : T;

type Mixed = string | number | boolean;
type Filtered = ExcludeString<Mixed>;  // number | boolean
// 分布过程: string→never, number→number, boolean→boolean
// never被忽略,结果为 number | boolean

infer关键字:在条件类型中提取类型

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

function getUser() {
    return { name: "李四", age: 30 };
}

type UserType = ReturnType<typeof getUser>;
// 等价于 { name: string; age: number }

// 提取Promise的泛型参数
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type Result = UnwrapPromise<Promise<string>>;        // string
type Result2 = UnwrapPromise<Promise<number[]>>;     // number[]
type Result3 = UnwrapPromise<string>;                  // string(非Promise原样返回)

// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;

type Item = ElementOf<string[]>;    // string
type Item2 = ElementOf<number[]>;   // number

映射类型:批量转换类型属性

// 把所有属性变为只读
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

// 把所有属性变为可选
type Partial<T> = {
    [P in keyof T]?: T[P];
};

// 把所有属性变为可空
type Nullable<T> = {
    [P in keyof T]: T[P] | null;
};

interface User {
    id: number;
    name: string;
    email: string;
}

type OptionalUser = Partial<User>;
// { id?: number; name?: string; email?: string; }

type ReadonlyUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email: string; }

模板字面量类型:字符串级别的类型操作

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

// API路径类型
type ApiVersion = "v1" | "v2";
type ApiEndpoint = `/api/${ApiVersion}/${string}`;
// 匹配: "/api/v1/users" "/api/v2/orders" 等

// 属性名 getter 方法生成
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface Person {
    name: string;
    age: number;
}

type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }

跨端小程序开发中,这个模式可以自动生成各端API的调用方法,减少手写声明的工作量。

实战:类型安全的事件总线

// 定义事件映射
interface EventMap {
    login: { userId: string; token: string };
    logout: void;
    message: { from: string; content: string };
    error: { code: number; message: string };
}

class TypedEventEmitter<T extends Record<string, any>> {
    private listeners: { [K in keyof T]?: Array<(payload: T[K]) => void> } = {};

    on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void {
        if (!this.listeners[event]) {
            this.listeners[event] = [];
        }
        this.listeners[event]!.push(handler);
    }

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

    off<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void {
        const arr = this.listeners[event];
        if (arr) {
            const idx = arr.indexOf(handler);
            if (idx >= 0) arr.splice(idx, 1);
        }
    }
}

// 使用
const bus = new TypedEventEmitter<EventMap>();

// 类型安全:payload类型自动推导
bus.on("login", (data) => {
    console.log(data.userId);   // string
    console.log(data.token);    // string
});

bus.emit("login", { userId: "u123", token: "tk456" }); // 类型正确
bus.emit("login", { userId: "u123" });                  // 编译报错: 缺少token
bus.emit("message", { from: "a", content: "hi" });     // 类型正确
bus.on("error", (data) => {
    console.log(data.code);     // number
    console.log(data.message);  // string
});

响应式布局项目中,事件总线的类型安全尤为重要——编译期拦截不匹配的事件类型,避免运行时的undefined错误。Web性能优化中,类型完整的代码也便于Tree-shaking,减少打包体积。

TypeScript类型体操的目标不是炫技,而是用编译期检查替代运行时调试。合理的类型约束让重构更安全、API更清晰、协作更顺畅。类型系统投入的学习成本会在项目维护阶段数倍返还。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-lei-xing-ti-cao-shi-zhan-fan-xing-yue-shu-yu/

(0)
小编小编
上一篇 11小时前
下一篇 11小时前

相关推荐