TypeScript 泛型详解:从基础到实战应用

一、开篇三问

  1. 你是否这么写过函数?

面对不确定传入和返回值的类型的时候,你是否将函数的参数和返回值类型写成any,这样子ts就失去了类型检查的意义,只能知道数据类型是any,而不知道具体的类型。

  1. 想让它 既 支持 number、string、boolean,又 不失去类型推断?

  2. 这就是泛型(Generic)存在的意义:一套代码 → 多种类型 → 编译期检查 → 运行期正确。

  3. 一句话理解泛型概念:

泛型 = 把 类型 当成 参数 传进去。

就像函数参数 value: T,只是 T 不是值,而是"某个类型"。

二、泛型基础:语法与核心概念

2.1 泛型函数

泛型通过类型变量 表示未指定的类型,在函数调用或类实例化时被具体类型替换。类型变量通常用<T>表示(T即 "Type" 的缩写)。

javascript 复制代码
// T 代表"任意类型",在调用时由使用者决定
function identity<T>(arg: T): T {
  return arg;
}

// 调用方式 1:显式指定类型
const str = identity<string>('Hello');

// 调用方式 2:让 TS 自动推断
const num = identity(42); // 推断为 number

2.2 泛型函数与多类型参数

泛型函数支持多个类型变量,用于处理多种类型的输入输出场景。

javascript 复制代码
// 多类型参数:将两个不同类型的值组合为元组
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

// 调用时自动推断类型为[string, number]
const result = pair("age", 25);
console.log(result); // ["age", 25]

泛型也可用于类的方法(函数)中:

javascript 复制代码
class Container<T> {
  private value: T;
  constructor(initialValue: T) {
    this.value = initialValue;
  }
  // 泛型方法:返回T类型
  getValue(): T {
    return this.value;
  }
  // 泛型方法:接收T类型参数
  setValue(newValue: T): void {
    this.value = newValue;
  }
}

// 使用时指定类型为string
const stringContainer = new Container<string>("init");
stringContainer.setValue("update");
// stringContainer.setValue(12)   // 报错指定的参数类型为string,但传入的参数类型为number

2.3 泛型接口

泛型接口允许接口成员的类型由调用者指定,适用于定义通用数据结构或函数类型。

javascript 复制代码
// 定义泛型接口:描述键值对结构
interface KeyValuePair<K, V> {
  key: K;
  value: V;
}

// 使用时指定K为string,V为number
const agePair: KeyValuePair<string, number> = {
  key: "age",
  value: 12
};

// 泛型接口描述函数类型
interface Transformer1<T, U> {
  (input: T): U;
}

// 实现接口:将number转为string
const numberToString: Transformer1<number, string> = (num) => {
  return num.toString();
};

2.4 泛型类

泛型类通过在类名后添加<T>定义,类内部可使用该类型参数。

javascript 复制代码
// 泛型类:实现栈数据结构
class Stack<T> {
    // 定义栈
    private items:T[] =[]
     // 入栈:接收T类型元素
    push(item:T){
        this.items.push(item)
    }
    // 出栈:返回T类型元素
    pop():T | undefined{
        return this.items.pop()
    }
}
// 实例化number类型的栈
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2(类型为number)

// 实例化string类型的栈
const stringStack = new Stack<string>();
stringStack.push("a");
console.log(stringStack.pop()); // "a"(类型为string)

2.5 泛型参数默认值

为泛型参数设置默认类型,当未显式指定类型时自动使用默认值:

javascript 复制代码
// 为T设置默认类型string
class Box<T> {
  value: T;
  constructor(value: T) {
    this.value = value;
  }
  show(){
      return this.value;
  }
}

// 未指定类型,默认使用string
const strBox = new Box("hello");
console.log(strBox.show());
// 显式指定为number
const numBox = new Box<number>(123);
console.log(numBox.show());

2.6 注意:静态成员与泛型的限制

泛型类的静态成员不能使用类的类型参数,因为静态成员属于类本身(加载时确定),而类型参数在实例化时才确定:

三、泛型约束

泛型的灵活性可能导致类型操作不安全(例如访问不存在的属性)。通过泛型约束,我们可以限制类型参数的范围,确保操作合法。

3.1 用extends约束类型

通过extends关键字,要求泛型必须满足某个接口或类型:

javascript 复制代码
// 定义约束:必须包含length属性
interface Haslengrh{
    length:number;
}
// 约束T必须符合HasLength
function getlength<T extends Haslengrh>(arg:T):T{
    console.log(arg.length)
    return arg;
}
// 合法:字符串有length属性
getlength("hello")
// 合法:字符串有length属性
getlength([1,2,3])
// 合法:字符串有length属性
getlength({length:10})
// 错误:数字没有length属性
// getlength(123)

3.2 用keyof约束对象属性

结合keyof操作符(获取对象所有属性名),可确保访问的属性存在于对象中:

javascript 复制代码
// T为对象类型,K为T的属性名
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]; // 安全访问obj的key属性
}

const user = { name: "Alice", age: 25 };
// 合法:"name"是user的属性
getProperty(user, "name"); // "Alice"
// 错误:"gender"不是user的属性
getProperty(user, "gender"); 

四、最佳实践与常见误区

✅ 推荐 ❌ 不推荐
命名使用 T / K / V 等约定 使用无意义的长名称
使用 extends 约束输入 滥用 any 放弃类型检查
组合 Utility Types 生成新类型 重复声明相似接口
静态方法独立定义泛型 在静态成员中引用类级泛型

相关推荐
卸任4 小时前
Vibe coding工具使用心得
前端·ai编程
brien95 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance5 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M5 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写5 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李5 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易5 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
清晨Coding6 小时前
删了 Cookie 还能自动登录?Google系网站登录态自动恢复的原理分析
javascript
风骏时光牛马6 小时前
前沿视界
前端
kyriewen6 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员