指南

泛型与类型复用

通过泛型实现类型复用,掌握泛型函数、接口、约束及实用工具类型的使用技巧

🎯 引言

学会泛型,你就能写出更灵活、更可复用的前端代码。泛型让函数、接口和数组都能适应多种类型,避免重复造轮子。本文将带你从零开始了解泛型函数与接口,掌握泛型约束,还会介绍实用的工具类型,帮你写出干净又高效的代码。


🌀 泛型函数与接口:为类型插上“翅膀”

泛型最核心的作用就是类型参数化,简单来说,就是让代码里的类型像变量一样灵活传入。

泛型函数基础示例

function identity<T>(arg: T): T {
    return arg;
}

const output1 = identity<string>('hello');
const output2 = identity(123); // 类型推断为 number

这里的 T 就像占位符,调用时传入具体类型。它让函数能处理任意类型,还保证类型安全。

泛型接口基础示例

接口中用泛型定义属性或者方法的类型,让接口更灵活:

interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identityFn<T>(arg: T): T {
    return arg;
}

const myIdentity: GenericIdentityFn<number> = identityFn;

GenericIdentityFn<T> 表示一个函数签名,接收 T 类型参数,返回 T 类型结果。这样写的接口,能适应多种类型的函数。


🔐 泛型约束:给泛型加个“安全带”

泛型虽然灵活,但有时需要限制类型,确保类型参数满足某个条件,避免出现没有预料的错误。

约束示例

假设我们想获取传入对象的某个属性值,但必须先确保对象有该属性:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

loggingIdentity([1, 2, 3]); // 数组有 length,合法
// loggingIdentity(123); // 报错,number 没有 length

这里 T extends Lengthwise 表示 T 必须符合 Lengthwise 接口,即有 length 属性。这样就防止了在没有 length 的类型上调用 length


🌈 泛型在函数、数组和接口中的应用

泛型的威力不仅限于简单函数和接口,下面我们用几个场景来加深理解。

泛型函数:处理多类型数据

function reverseArray<T>(items: T[]): T[] {
    return items.reverse();
}

const nums = [1, 2, 3];
const reversedNums = reverseArray(nums); // 类型自动推断为 number[]

这个泛型函数能接受任意类型数组,返回相同类型的逆序数组。

泛型数组:声明灵活的数组类型

let strings: Array<string> = ['a', 'b', 'c'];
let numbers: Array<number> = [1, 2, 3];

Array<T> 是数组的泛型写法,声明数组元素类型,方便类型推断和代码提示。

泛型接口:为复杂数据建模

interface ApiResponse<T> {
    data: T;
    status: number;
    message: string;
}

const response: ApiResponse<string[]> = {
    data: ['apple', 'banana'],
    status: 200,
    message: 'Success',
};

用泛型接口包装不同类型的数据结构,提升代码复用。


🛠️ 简单介绍实用工具类型 Partial

TypeScript 提供了一些内置工具类型来方便类型操作,Partial<T> 是其中的一个常用类型。

Partial 的作用

它将类型 T 里的所有属性变成可选的,比如:

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

const updateUser: Partial<User> = {
    name: 'Tom',
};

这样 updateUser 可以只写部分属性,特别适合做更新接口参数或组件 props。

Partial 的实现原理(简要)

type Partial<T> = {
    [P in keyof T]?: T[P];
};

它用映射类型遍历 T 所有属性,把每个属性都变成可选的。

泛型让代码更灵活,但不要滥用。泛型参数越多,代码越难读懂。做到适当使用,结合代码注释,提升可维护性。

🔍 与 any 类型区别

TypeScript 中泛型(generics)和 any 的区别,虽然它们看起来都能「接受任意类型」,但本质和用途是完全不同的。

any 是 逃避类型检查

function log(input: any) {
    console.log(input);
}
  • input 可以是任何类型。
  • 你完全失去了类型系统的帮助,没有类型检查、没有自动提示、没有错误提示。

any 只建议在 非常临时或无奈的场景下使用,比如处理第三方库返回的数据不确定时做个兜底。

泛型是类型占位符 + 类型安全

function log<T>(input: T): T {
    console.log(input);
    return input;
}
  • T 是一个类型变量,调用的时候再指定(或者自动推导)。
  • 类型检查仍然存在
  • 可以根据传入的类型,推导出返回值的类型
  • 有代码提示

🧾 小节总结

  • 泛型让函数、接口等支持多种类型,实现代码复用和类型安全
  • 泛型约束为泛型参数加限制,保证类型符合预期,避免运行错误
  • 内置工具类型如 Partial<T> 能简化类型操作,提升开发效率

❓ 知识问答(Q&A)

Q:泛型函数和普通函数有什么区别?

A:泛型函数使用类型参数,支持多种类型的输入输出;普通函数类型固定或不灵活。

Q:泛型约束的作用是什么?

A:限制泛型参数必须符合某个接口或类型,保证函数内部安全访问特定属性或方法。

Q:Partial 的主要用途是什么?

A:把类型 T 的所有属性变成可选,方便声明只需要部分属性的对象。


🧪 小练习

题目 1 :编写一个泛型函数,它可以接受一个数组和一个元素,将元素添加到数组末尾并返回新数组。

function appendToArray<T>(array: T[], element: T): T[] {
    // 在这里实现
}

题目 2 :定义一个泛型接口 Result<T>,包含 success: booleandata: T 两个属性,并用它来声明一个返回字符串数组的结果对象。

interface Result<T> {
    // 定义属性
}

const res: Result<string[]> = {
    // 填写属性值
};

🎉 恭喜你已经掌握泛型函数、接口、约束和实用工具类型 Partial 技能啦!用泛型写代码,既灵活又安全,助你成为优秀的前端开发者!