TypeScript: TypeScript 泛型基础

最后更新:2026-08-26

泛型是 TypeScript 最强大的特性——它让你写"适用于多种类型"的代码,同时保持完整的类型安全。理解泛型,是掌握 TypeScript 的关键一步。

1. 为什么需要泛型

(1) 没有 泛型的困境

TYPESCRIPT
// 方案一:用 any——丢失类型信息
function firstAny(arr: any[]): any {
  return arr[0];
}

let n = firstAny([1, 2, 3]);    // n 是 any——不知道是 number
let s = firstAny(["a", "b"]);   // s 是 any——不知道是 string

// 方案二:为每种类型写函数——代码重复
function firstNumber(arr: number[]): number { return arr[0]; }
function firstString(arr: string[]): string { return arr[0]; }
// 无穷无尽...

(2) 泛型的解决方案

TYPESCRIPT
function first<T>(arr: T[]): T {
  return arr[0];
}

let n = first([1, 2, 3]);       // T 推断为 number → 返回 number
let s = first(["a", "b"]);      // T 推断为 string → 返回 string
let b = first([true, false]);   // T 推断为 boolean → 返回 boolean

console.log(n.toFixed(2));      // ✅ TypeScript 知道 n 是 number
console.log(s.toUpperCase());   // ✅ TypeScript 知道 s 是 string
📌 核心思想: <T> 是类型参数——调用时由传入的参数类型决定 T 是什么。一次定义,多种类型使用,且每种情况都有精确的类型信息。


2. 泛型函数

(1) 基本语法

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

// TypeScript 自动推断 T
let num = identity(42);       // T = number
let str = identity("hello");  // T = string

// 也可以显式指定 T(有时需要)
let result = identity<string>("hello");

(2) 多个类型参数

TYPESCRIPT
function pair<A, B>(first: A, second: B): [A, B] {
  return [first, second];
}

let p1 = pair("name", 42);        // [string, number]
let p2 = pair(true, [1, 2, 3]);   // [boolean, number[]]

(3) 泛型与数组

TYPESCRIPT
function map<T, U>(arr: T[], transform: (item: T) => U): U[] {
  return arr.map(transform);
}

let numbers = [1, 2, 3];
let strings = map(numbers, n => `第${n}个`);
// T = number, U = string → 返回 string[]

console.log(strings);  // ["第1个", "第2个", "第3个"]

▶ 示例:泛型工具函数

TYPESCRIPT
// 过滤数组
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
  return arr.filter(predicate);
}

// 查找数组
function find<T>(arr: T[], predicate: (item: T) => boolean): T | undefined {
  return arr.find(predicate);
}

// 数组分块
function chunk<T>(arr: T[], size: number): T[][] {
  let result: T[][] = [];
  for (let i = 0; i < arr.length; i += size) {
    result.push(arr.slice(i, i + size));
  }
  return result;
}

let nums = [1, 2, 3, 4, 5, 6, 7];
let evens = filter(nums, n => n % 2 === 0);
let found = find(nums, n => n > 5);
let groups = chunk(nums, 3);

console.log("偶数:" + evens);        // "偶数:2,4,6"
console.log("找到:" + found);        // "找到:6"
console.log("分块:" + JSON.stringify(groups));  // "[[1,2,3],[4,5,6],[7]]"
▶ 试一试

输出:

TEXT 📖 仅展示
偶数:2,4,6
找到:6
分块:[[1,2,3],[4,5,6],[7]]

3. 泛型接口

(1) 基本语法

TYPESCRIPT
interface Box<T> {
  value: T;
}

let stringBox: Box<string> = { value: "hello" };
let numberBox: Box<number> = { value: 42 };

(2) 泛型接口描述函数

TYPESCRIPT
interface Transformer<A, B> {
  (input: A): B;
}

let toString: Transformer<number, string> = (n) => String(n);
let toLength: Transformer<string, number> = (s) => s.length;

console.log(toString(42));       // "42"
console.log(toLength("hello"));  // 5

(3) 泛型接口描述仓库模式

TYPESCRIPT
interface Repository<T> {
  findById(id: string): T | null;
  findAll(): T[];
  save(item: T): void;
  delete(id: string): boolean;
}

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

class UserRepo implements Repository<User> {
  private data: User[] = [];

  findById(id: string): User | null {
    return this.data.find(u => u.id === id) ?? null;
  }

  findAll(): User[] {
    return this.data;
  }

  save(item: User): void {
    this.data.push(item);
  }

  delete(id: string): boolean {
    let len = this.data.length;
    this.data = this.data.filter(u => u.id !== id);
    return this.data.length < len;
  }
}

4. 泛型类

(1) 基本语法

TYPESCRIPT
class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }

  get size(): number {
    return this.items.length;
  }
}

let numStack = new Stack<number>();
numStack.push(1);
numStack.push(2);
numStack.push(3);
console.log(numStack.pop());   // 3
console.log(numStack.peek());  // 2
console.log(numStack.size);    // 2

let strStack = new Stack<string>();
strStack.push("a");
strStack.push("b");
console.log(strStack.pop());   // "b"

(2) 泛型类的静态成员

TYPESCRIPT
class GenericClass<T> {
  // static defaultValue: T;  // ❌ 静态成员不能引用类的类型参数

  // ✅ 静态方法需要自己的类型参数
  static create<U>(value: U): GenericClass<U> {
    let instance = new GenericClass<U>();
    instance.value = value;
    return instance;
  }

  constructor(public value: T) {}
}

let instance = GenericClass.create("hello");  // GenericClass<string>
📌 原因: 类的类型参数 T 是实例级别的——每个实例可以有不同的 T。静态成员属于类本身,不属于任何实例,所以不能引用实例的类型参数。


5. 泛型约束(extends)

默认情况下,泛型 T 可以是任何类型——有时这太宽泛了。用 extends 约束 T 必须满足某些条件:

(1) 约束为特定接口

TYPESCRIPT
interface HasLength {
  length: number;
}

// T 必须有 length 属性
function logLength<T extends HasLength>(value: T): void {
  console.log(`长度:${value.length}`);
}

logLength("hello");        // ✅ string 有 length
logLength([1, 2, 3]);      // ✅ 数组有 length
logLength({ length: 10 }); // ✅ 对象有 length
// logLength(42);          // ❌ number 没有 length

(2) 约束为另一个泛型

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

let user = { name: "Charlie", age: 20, email: "xiao@example.com" };

console.log(getProperty(user, "name"));   // ✅ "Charlie" —— K = "name"
console.log(getProperty(user, "age"));    // ✅ 20 —— K = "age"
// getProperty(user, "phone");            // ❌ "phone" 不是 user 的键

(3) 约束为构造函数

TYPESCRIPT
function createInstance<T>(Constructor: new () => T): T {
  return new Constructor();
}

class Dog {
  bark() { return "汪!"; }
}

let dog = createInstance(Dog);
console.log(dog.bark());  // "汪!"

▶ 示例:类型安全的对象合并

TYPESCRIPT
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

let defaults = { host: "localhost", port: 3000 };
let custom = { port: 8080, debug: true };

let config = merge(defaults, custom);
// 推断类型:{ host: string; port: number } & { port: number; debug: boolean }
// 简化后:{ host: string; port: number; debug: boolean }

console.log(config.host);   // "localhost"
console.log(config.port);   // 8080
console.log(config.debug);  // true
▶ 试一试

6. 常用内置泛型类型

TypeScript 内置了许多实用的泛型工具类型:

类型 作用 示例
Array<T> 数组类型 Array<number>
Promise<T> Promise 类型 Promise&lt;string&gt;
Record<K, V> 键值对对象 Record<string, number>
Partial<T> 所有属性可选 Partial<Config>
Required<T> 所有属性必选 Required<Config>
Readonly<T> 所有属性只读 Readonly<Config>
Pick<T, K> 选取部分属性 Pick<User, "name" | "age">
Omit<T, K> 排除部分属性 Omit<User, "email">
Exclude<T, U> 从联合类型排除 Exclude<"a"|"b"|"c", "a">
Extract<T, U> 从联合类型提取 Extract<"a"|"b"|"c", "a"|"b">
ReturnType<T> 函数返回值类型 ReturnType<typeof fn>
Parameters<T> 函数参数类型元组 Parameters<typeof fn>
TYPESCRIPT
// 实际使用示例
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// 创建用户时 id 不需要提供
type CreateUser = Omit<User, "id">;

// 更新用户时所有字段都可选
type UpdateUser = Partial<User>;

// 用户摘要只有部分字段
type UserSummary = Pick<User, "id" | "name">;

let newUser: CreateUser = { name: "Charlie", email: "xiao@example.com", age: 20 };
let update: UpdateUser = { age: 21 };
let summary: UserSummary = { id: 1, name: "Charlie" };

❓ 常见问题

Q 泛型类型参数用什么命名?
A 惯例用单个大写字母——T(Type)、U/V/W(后续类型)、K(Key)、V(Value)、E(Element)。多个单词时用 PascalCase:TItemTResponse。团队统一即可。
Q 什么时候需要显式指定泛型类型?
A 大多数时候 TypeScript 能从参数推断泛型类型。需要显式指定的场景:(1) 函数没有参数或参数类型不能确定 T (2) 推断结果太宽泛(如推断为联合类型) (3) 函数返回类型需要更精确的控制。
Q 泛型约束 extends 和类继承 extends 有什么区别?
A 语法相同但语义不同。泛型约束 T extends HasLength 表示"T 至少要有 HasLength 的结构"——是类型兼容关系。类继承 class Dog extends Animal 表示"Dog 是 Animal 的子类"——是面向对象关系。
Q Record<string, number>{ [key: string]: number } 有什么区别?
A 完全等价。Record 是内置的泛型工具类型,底层就是索引签名。Record<string, number> 更简洁,{ [key: string]: number } 更灵活(可以加已知属性)。

📖 小节

📝 作业

  1. 基础题(难度⭐):写一个泛型函数 wrapInArray<T>(value: T): T[],把单个值包装成数组。测试 number 和 string 类型。
  2. 进阶题(难度⭐⭐):写一个泛型函数 pluck<T, K extends keyof T>(items: T[], key: K): T[K][],从对象数组中提取指定属性的值。例如 pluck([{name: "a", age: 1}, {name: "b", age: 2}], "name") 返回 ["a", "b"]
  3. 挑战题(难度⭐⭐⭐):实现一个泛型类 DataStore<T extends { id: string }>,提供 save/find/delete 方法。id 属性通过泛型约束确保存在。创建 DataStore<{ id: string; title: string }> 实例测试增删查功能。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏