文章

泛型基础与应用场景

泛型基础与应用场景

一句话概括

泛型就是给类型留个”占位符”——写代码时不写死类型,用的时候再填,既能复用代码又不丢类型安全。它是 TypeScript 从”能用”到”好用”的分界线。

核心知识点

1. 泛型函数:一份代码,N 种类型

1
2
3
4
5
6
7
8
// ❌ 不用泛型:any 丢了类型信息
function echo(x: any): any { return x; }
const a = echo("hi"); // a 是 any,IDE 没法提示 .toUpperCase()

// ✅ 泛型:类型流过函数不丢失
function echo<T>(x: T): T { return x; }
const b = echo("hi"); // b 自动推断为 string ✨
const c = echo(42);   // number

2. 泛型约束 extends:限制”能传什么”

1
2
3
4
5
6
7
8
9
10
11
// ❌ 直接访问 T 的属性会报错:T 可能没有 length
function logLen<T>(x: T) { console.log(x.length); } // 报错!

// ✅ extends 约束:T 必须满足某个形状
function logLen<T extends { length: number }>(x: T) {
  console.log(x.length); // 🔒 安全
}
logLen("hello");     // ✅
logLen([1, 2, 3]);   // ✅
logLen({ length: 5 }); // ✅
// logLen(123);       // ❌ number 没有 length

3. 泛型接口:API 响应的标准建模

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface ApiResponse<T> {
  code: number;
  data: T;
  message: string;
}

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

const res: ApiResponse<User> = {
  code: 200,
  data: { id: 1, name: "Alice" },
  message: "ok"
};
// res.data.name 自动补全 ✅

这是后端接口定义里最实用的模式,几乎所有团队的 ts 规范都会要求对 API 返回做泛型包裹。

4. 泛型 + keyof:类型安全的属性访问

1
2
3
4
5
6
7
8
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "Alice", age: 30, active: true };
getProp(user, "name");  // 返回 string ✅
getProp(user, "age");   // 返回 number ✅
// getProp(user, "email"); // ❌ 编译报错,email 不存在

5. 泛型工具类型速览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type Draft = Partial<Todo>;     // 全部可选 → 编辑表单场景
type Preview = Pick<Todo, "title" | "completed">; // 只取部分 → 列表展示
type Info = Omit<Todo, "completed">;  // 排除某字段 → 脱敏返回
type Frozen = Readonly<Todo>;  // 全部只读 → 常量配置

// 泛型 Hook 示例
function useLocalStorage<T>(key: string, initial: T) {
  const [val, setVal] = useState<T>(() => {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : initial;
  });
  // ...
}

「其实你每天都在用」

  1. axios/fetch 封装request<T>(url) 返回 Promise<T>,不写泛型响应就是 any
  2. React useStateuseState<User[]>([]) 里的菱形括号就是泛型
  3. 数组 map/filter 的类型推断[1,2,3].map(x => x * 2) 能知道返回 number[],全靠泛型签名
  4. Ant Design / Element Plus 表格列定义columns: TableColumn<User>[] 确保 dataIndex 不写错
  5. Redux / Zustand 的 selectoruseStore(state => state.user.name) 一路类型推导到 string

常见误解(FAQ)

❌ 误区 1:泛型就是用来对付 any 的

泛型和 any 是完全相反的设计哲学。any 是”放弃类型检查”,泛型是”推迟类型指定但最终确保安全”。一个函数写了 any,调用方得不到任何提示;写了泛型,TS 在调用处帮你做精确推导。

❌ 误区 2:泛型参数必须显式传

大多数情况下不需要。TypeScript 的类型推断非常强大——你传了 [1,2,3],T 自动推断为 number。只有推断失败或你需要更宽的类型时才手动传。

❌ 误区 3:T extends {}T extends object 是一回事

不一样。{} 匹配除 null/undefined 外的所有类型(包括 string、number 等原始类型),object 只匹配引用类型(对象、数组、函数)。约束函数参数用 extends object 时原始类型会直接报错。

❌ 误区 4:泛型只在 TypeScript 里有,编译后就没了

泛型确实在编译后被擦除(类型擦除),但 C#、Java、Kotlin、Rust 等语言都有泛型,而且有些是运行时保留的。TypeScript 的泛型是纯编译时机制,这是它”零运行时开销”的代价和优势。

一句话总结

泛型不是银弹,但它让 TypeScript 从「给 JS 写类型注释」升级到「类型像代码一样可复用可推导」——理解它就理解了 TS 类型系统的一半。

本文由作者按照 CC BY 4.0 进行授权