ToolkitX
知识库工具箱

泛型

泛型函数、泛型类、泛型约束

25min·进阶

01. 泛型——类型的变量

泛型就是把类型也当成参数——你可以写一个函数、类或接口,不指定具体的类型,而是留一个类型变量(T、U 等),使用者调用时再填具体类型。 为什么需要泛型?假设你写了一个 identity 函数——传什么返回什么。不用泛型的话只能用 any,丢了类型安全。用泛型:identity<string>('hello') 返回 string,identity<number>(42) 返回 number。 泛型变量通常用 T、U、V 表示,多个泛型参数逗号分隔 <T, U>。T 是 Type 的缩写,也可以用有意义的名字如 <TData, TError>。 泛型不是运行时存在的——编译后会被擦除。它纯粹是编译时的类型检查工具。
typescript
// 不用泛型——丢了类型信息
function identityAny(arg: any): any {
  return arg;
}
get result = identityAny('hello');  // result: any

// 用泛型——保留类型信息
function identity<T>(arg: T): T {
  return arg;
}
get result2 = identity('hello');   // result2: string (自动推断)
get result3 = identity<number>(42); // result3: number (显式指定)
泛型参数大部分情况可以自动推断,不需要显式写 <Type>。TS 根据传入参数的类型自动推导 T。只在推断不出来或需要强制类型时才显式写。

02. 泛型约束——extends 限制类型范围

泛型不加限制的话 T 可以是任何类型——但你如果用到了 .length 属性,不是所有类型都有 .length。这时需要泛型约束。 用 extends 约束泛型:<T extends { length: number }> 表示 T 必须是有 length 属性的类型(string、Array、NodeList 等)。 泛型约束的另一个常用场景——<T extends keyof SomeType> 确保 T 是某个类型的属性名:function getProperty<T, K extends keyof T>(obj: T, key: K)。 多个约束可以用交叉类型:<T extends A & B>。 还有个实用的泛型默认值:<T = string> 不传泛型参数时默认为 string。
typescript
// 泛型约束——要求有 length 属性
function logLength<T extends { length: number }>(item: T): T {
  console.log(item.length);
  return item;
}
logLength('hello'); // OK——string 有 length
logLength([1, 2, 3]); // OK——数组有 length
// logLength(123); // 报错——number 没有 length

// 约束为某个类型的键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

// 泛型默认值
function createList<T = string>(): T[] {
  return [];
}
get list = createList();   // list: string[]
get list2 = createList<number>(); // list2: number[]

03. 泛型在类与接口中的使用

泛型最经典的应用场景: Repository 模式——数据库操作封装。class Repository<T> 对任何实体都能提供 findById、save、delete 操作。T 是实体类型。 Result/Option 类型——Rust 风格的错误处理。type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }。 API 响应包装——interface ApiResponse<T> { code: number; data: T; message: string }。不同接口返回不同 data 类型。 Event Emitter——class EventEmitter<T extends Record<string, any>>。T 是事件名到参数的映射。
typescript
// Repository 模式
class Repository<T extends { id: number }> {
  private items: T[] = [];
  
  findById(id: number): T | undefined {
    return this.items.find(item => item.id === id);
  }
  
  save(item: T): void {
    this.items.push(item);
  }
}

interface User { id: number; name: string; }
get userRepo = new Repository<User>();

// API 响应包装
interface ApiResponse<T> {
  code: number;
  data: T;
  message: string;
}

type UserResponse = ApiResponse<User>;
type ListResponse = ApiResponse<User[]>;

04. 泛型的高级模式——条件泛型

泛型跟条件类型结合能做很酷的事: 根据泛型参数的值返回不同类型——T extends 'string' ? string : T extends 'number' ? number : never。 泛型参数可以是另一个泛型——<T extends Array<infer U>> 提取数组元素的类型。 模板字面量类型的泛型——TS 4.1+ 支持字符串模板作为类型:type EventName<T extends string> = `on${Capitalize<T>}`。type ChangeEvent = EventName<'change'>; // 'onChange'。 这些高级泛型模式是类型体操的范畴,日常业务开发用到的少,但写通用工具库时会频繁用到。
typescript
// 条件泛型——根据输入类型返回不同输出
interface StringMap { string: string; number: number; boolean: boolean; }
type GetType<T extends keyof StringMap> = StringMap[T];

type A = GetType<'string'>;  // string
get B = GetType<'number'>;  // number

// 提取 Promise 的值类型
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type P1 = Unwrap<Promise<string>>;  // string
type P2 = Unwrap<number>;           // number

// 模板字面量泛型
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickHandler = EventName<'click'>;  // 'onClick'

05. 日常开发中最常用的泛型模式

不用记所有高级泛型,日常开发 90% 就这几个模式: 1. 带泛型的函数——useState<T>、Array<T>、Promise<T>。TypeScript 自带,不需要自己写。 2. 泛型约束——确保传入的对象有某个属性,确保参数是对象的合法 key。 3. 泛型响应包装——所有 API 调用的统一返回格式。 4. 泛型工具类型——Partial、Pick、Omit、Record。四个组合能解决大部分对象类型变换。 5. React 里的泛型——useState<User>()、useRef<HTMLInputElement>(null)、forwardRef<HTMLDivElement>。
typescript
// 日常泛型模式

// 1. 带泛型的 hooks
const [user, setUser] = useState<User | null>(null);
const ref = useRef<HTMLInputElement>(null);

// 2. 泛型响应
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
  const res = await fetch(url);
  return res.json();
}

// 3. 泛型事件处理
function handleChange<T extends HTMLInputElement>(e: React.ChangeEvent<T>) {
  console.log(e.target.value);
}

// 4. 泛型回调
function debounce<T extends (...args: any[]) => any>(
  fn: T, delay: number
): (...args: Parameters<T>) => void {
  let timer: ReturnType<typeof setTimeout>;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

知识测验

1/5正确 0

泛型的主要作用是什么?

下一节

高级类型

下一节