前置知识: TypeScript

泛型约束与默认值

00:00
3 min Intermediate 2026/6/14

泛型约束、默认类型参数与条件泛型

概述

泛型约束和默认值是 TypeScript 泛型系统的重要补充机制。泛型约束(extends)限制类型参数的范围,确保只有满足条件的类型才能作为参数传入。默认类型参数允许在未显式指定类型时使用预设类型,简化泛型的使用。条件泛型结合条件类型和泛型约束,根据类型参数的关系选择不同的类型结果。掌握这些机制能够编写更安全、更灵活的泛型代码。

基础概念

泛型约束(extends):使用 T extends U 语法限制类型参数 T 必须满足类型 U 的结构。编译器会检查传入的类型是否兼容约束。

keyof 约束K extends keyof T 限制 K 必须是 T 的键类型,常用于类型安全的属性访问。

默认类型参数:使用 T = DefaultType 语法类型参数提供默认值调用时可以省略有默认值参数

条件泛型:在泛型中使用条件类型 T extends U ? X : Y类型参数关系返回不同的类型。常用于函数重载泛型版本。

快速上手

泛型约束

// 约束类型参数必须有 length 属性
interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(value: T): void {
  console.log(value.length); // 安全访问 length
}

logLength('hello'); // 正确:string 有 length
logLength([1, 2, 3]); // 正确:数组有 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: 30 };
const name = getProperty(user, 'name'); // string
const age = getProperty(user, 'age'); // number
// getProperty(user, 'email');          // 错误:'email' 不是 user 的键

默认类型参数

// 带默认值的泛型接口
interface PaginatedResponse<T, Meta = { total: number; page: number }> {
  data: T[];
  meta: Meta;
}

// 使用默认 Meta 类型
const response1: PaginatedResponse<User> = {
  data: [],
  meta: { total: 0, page: 1 },
};

// 自定义 Meta 类型
interface CustomMeta {
  total: number;
  page: number;
  hasNext: boolean;
}
const response2: PaginatedResponse<User, CustomMeta> = {
  data: [],
  meta: { total: 0, page: 1, hasNext: false },
};

条件泛型

// 根据类型参数选择不同的返回类型
type ApiResponse<T, E = string> = { success: true; data: T } | { success: false; error: E };

// 事件处理器类型:无参数时返回 () => void
type EventHandler<T> = T extends undefined ? () => void : (payload: T) => void;

type ClickHandler = EventHandler<undefined>; // () => void
type InputHandler = EventHandler<{ value: string }>; // (payload: { value: string }) => void

详细用法

多重约束

// 使用交叉类型实现多重约束
interface HasId {
  id: string | number;
}

interface HasTimestamp {
  createdAt: Date;
  updatedAt: Date;
}

// T 必须同时满足 HasId 和 HasTimestamp
function updateEntity<T extends HasId & HasTimestamp>(entity: T, updates: Partial<T>): T {
  return {
    ...entity,
    ...updates,
    updatedAt: new Date() as any,
  };
}

// 使用
const article = { id: 1, title: '标题', createdAt: new Date(), updatedAt: new Date() };
updateEntity(article, { title: '新标题' }); // 正确

泛型工厂函数

// 类型安全的工厂函数
function createFactory<T>() {
  return {
    create(props: T): T {
      return { ...props };
    },
    update(entity: T, props: Partial<T>): T {
      return { ...entity, ...props };
    },
    validate(entity: T, schema: { [K in keyof T]?: (value: T[K]) => boolean }): boolean {
      return Object.entries(schema).every(([key, validator]) =>
        validator ? validator(entity[key as keyof T]) : true
      );
    },
  };
}

interface Product {
  name: string;
  price: number;
  inStock: boolean;
}

const productFactory = createFactory<Product>();
const product = productFactory.create({ name: '商品', price: 99, inStock: true });
const updated = productFactory.update(product, { price: 79 });

递归泛型约束

// 深度部分类型
type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;

// 深度更新函数
function deepUpdate<T extends object>(target: T, updates: DeepPartial<T>): T {
  const result = { ...target };
  for (const key in updates) {
    const updateValue = updates[key];
    const targetValue = target[key];
    if (
      typeof updateValue === 'object' &&
      updateValue !== null &&
      typeof targetValue === 'object' &&
      targetValue !== null
    ) {
      result[key] = deepUpdate(targetValue, updateValue);
    } else if (updateValue !== undefined) {
      result[key] = updateValue as T[Extract<keyof T, string>];
    }
  }
  return result;
}

// 使用
const config = {
  api: { baseURL: 'https://api.example.com', timeout: 5000 },
  features: { darkMode: true, analytics: false },
};

const updated = deepUpdate(config, {
  api: { timeout: 10000 },
});

条件类型与泛型约束

// 根据输入类型推断输出类型
function processValue<T extends string | number>(
  value: T
): T extends string ? { text: string; length: number } : { value: number; squared: number } {
  if (typeof value === 'string') {
    return { text: value, length: value.length } as any;
  }
  return { value: value as number, squared: (value as number) ** 2 } as any;
}

const stringResult = processValue('hello');
// { text: string; length: number }
const numberResult = processValue(5);
// { value: number; squared: number }

常见场景

类型安全的 EventEmitter

interface EventMap {
  click: { x: number; y: number };
  change: { value: string };
  submit: undefined;
}

class TypedEventEmitter<T extends Record<string, any>> {
  private listeners = new Map<keyof T, Set<Function>>();

  on<K extends keyof T>(
    event: K,
    handler: T[K] extends undefined ? () => void : (payload: T[K]) => void
  ): () => void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(handler);
    return () => this.listeners.get(event)?.delete(handler);
  }

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

const emitter = new TypedEventEmitter<EventMap>();
emitter.on('click', ({ x, y }) => console.log(x, y)); // 正确
emitter.on('submit', () => console.log('submitted')); // 正确
emitter.emit('change', { value: 'new' }); // 正确

类型安全的 API 客户端

interface Endpoints {
  '/users': {
    GET: { response: User[]; query?: { page: number } };
    POST: { response: User; body: Omit<User, 'id'> };
  };
  '/users/:id': {
    GET: { response: User; params: { id: string } };
  };
}

async function api<Path extends keyof Endpoints, Method extends keyof Endpoints[Path]>(
  path: Path,
  options: {
    method?: Method;
    params?: Endpoints[Path][Method] extends { params: infer P } ? P : never;
    query?: Endpoints[Path][Method] extends { query: infer Q } ? Q : never;
    body?: Endpoints[Path][Method] extends { body: infer B } ? B : never;
  }
): Promise<Endpoints[Path][Method] extends { response: infer R } ? R : never> {
  // 实现...
  return {} as any;
}

// 使用
const users = await api('/users', { method: 'GET', query: { page: 1 } });
// User[]
const user = await api('/users/:id', { method: 'GET', params: { id: '1' } });
// User

注意事项

  • 约束顺序:有默认值类型参数必须在没有默认值参数之后。例如 <T, U = string> 是正确的,<T = string, U>错误的。
  • 约束默认值关系默认类型必须满足约束。例如 <T extends string = number>错误的,因为 number 不满足 string 约束
  • 泛型推断:当 TypeScript 可以从参数推断类型时,不需要显式指定。但约束不会参与推断,只用于验证推断结果
  • 条件泛型的返回类型条件泛型的返回类型可能很复杂,建议为使用条件泛型的函数添加详细的 JSDoc 注释

进阶用法

泛型约束链

// 多层约束链:逐步细化类型
type EntityWithId = { id: string };
type EntityWithTimestamps = EntityWithId & { createdAt: string; updatedAt: string };
type SoftDeletable = EntityWithTimestamps & { deletedAt: string | null };

// 通用仓库接口
interface Repository<T extends EntityWithId> {
  findById(id: string): Promise<T | null>;
  save(entity: T): Promise<T>;
  delete(id: string): Promise<void>;
}

// 带时间戳的仓库
interface TimestampedRepository<T extends EntityWithTimestamps> extends Repository<T> {
  findUpdatedSince(date: string): Promise<T[]>;
}

// 软删除仓库
interface SoftDeleteRepository<T extends SoftDeletable> extends TimestampedRepository<T> {
  softDelete(id: string): Promise<void>;
  findActive(): Promise<T[]>;
}

泛型与类装饰器

// 类型安全的混入函数
type Constructor<T = {}> = new (...args: any[]) => T;

function withValidation<T extends Constructor<{ [key: string]: any }>>(Base: T) {
  return class extends Base {
    private errors: Map<string, string> = new Map();

    validate(): boolean {
      this.errors.clear();
      for (const [key, value] of Object.entries(this)) {
        if (typeof value === 'string' && value.trim() === '') {
          this.errors.set(key, `${key} 不能为空`);
        }
      }
      return this.errors.size === 0;
    }

    getErrors(): ReadonlyMap<string, string> {
      return this.errors;
    }
  };
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式