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 生成新类型 重复声明相似接口
静态方法独立定义泛型 在静态成员中引用类级泛型

相关推荐
hpoenixf16 分钟前
2026 年前端面试问什么
前端·面试
还是大剑师兰特23 分钟前
Vue3 中的 defineExpose 完全指南
前端·javascript·vue.js
泯泷41 分钟前
阶段一:从 0 看懂 JSVMP 架构,先在脑子里搭出一台最小 JSVM
前端·javascript·架构
mengchanmian1 小时前
前端node常用配置
前端
华洛2 小时前
利好打工人,openclaw不是企业提效工具,而是个人助理
前端·javascript·产品经理
xkxnq2 小时前
第六阶段:Vue生态高级整合与优化(第93天)Element Plus进阶:自定义主题(变量覆盖)+ 全局配置与组件按需加载优化
前端·javascript·vue.js
A黄俊辉A3 小时前
vue css中 :global的使用
前端·javascript·vue.js
小码哥_常3 小时前
被EdgeToEdge适配折磨疯了,谁懂!
前端
小码哥_常3 小时前
从Groovy到KTS:Android Gradle脚本的华丽转身
前端
灵感__idea3 小时前
Hello 算法:复杂问题的应对策略
前端·javascript·算法