TypeScript泛型详解:从基础到约束与实战

作者:闭包旅人

时间:2026-10-25

浏览:77

本文深入讲解TypeScript中泛型的核心概念,涵盖形式类型参数的定义、必选与可选参数的规则、泛型约束的实现方式,以及泛型函数在类型推断中的应用。通过具体代码案例,解析如何灵活使用泛型定义接口与类,提升代码复用性与类型安全性。

文章目录

泛型程序设计代表了一种特定的编程风格或范式,旨在通过参数化类型来增强代码的通用性和复用性。

通过具体案例展示如何使传入参数的类型与返回值的类型保持一致,这是理解泛型的基础场景。

function identify<T>(arg: T): T {// 当前的T没有任何约束 它可以是任何类型
    return arg;
}
 
const foo = identify('foo'); // foo的类型是'foo'
const bar = identify('true'); // bar的类型是true

在泛型定义中,形式类型参数用于占位,代表调用时确定的具体类型。

形式类型参数T可以指定默认类型为boolean,这意味着若未显式提供类型参数,将自动使用boolean。

<T = boolean>

类型参数可分为必选和可选两类,区别在于是否提供了默认类型值。

当形式类型参数未提供默认类型时,该参数为必选类型参数,例如定义中仅写<T>。

若形式类型参数提供了默认类型值,则该参数为可选类型参数,例如定义中写<T = boolean>。

在形式类型参数列表中,必选类型参数不能出现在可选类型参数之后,否则会导致编译错误。

<T = boolean, U> // 错误 <T, U = boolean> // 正确

泛型约束允许对形式类型参数施加限制,限定其实际类型的范围,这种限制机制称为泛型约束。

泛型约束可以定义类型参数的实际类型的最大范围,确保传入的类型符合特定接口或结构要求。

interface point {
    x: number,
    y: string
}
 
function getPoint<T extends point>(args: T): T {
    return args
}
 
console.log(getPoint({x: 123, y: 456})); // 错误
console.log(getPoint({x: 123, y: '456'})); // 正确
console.log(getPoint({x: 123, y: '456', z: 678})); // 正确
//参数的前俩个必须有并且类型必须正确  否则错误

在定义泛型时,可以同时指定泛型约束和默认类型,以提供更灵活的类型处理机制。

<T extends number = 0 | 1>

泛型约束直接作用于类型参数,限制其可接受的具体类型集合。

<T, U extends T>
<T extends U, U>

形式类型参数不允许直接或间接地将其自身作为约束类型,这避免了循环依赖的类型定义错误。

<T extends T> // 错误
<T extends U, U extends T> // 错误

若类型参数T未声明泛型约束,其基约束默认为空对象类型字面量“{}”。除undefined和null外,其他任何类型均可赋值给该空对象类型。

<T> // 类型参数T的基数约为“{}”类型

泛型函数是指函数签名中包含类型参数的函数,它允许函数在处理不同类型数据时保持类型安全。

若函数的形参中包含类型参数,则该函数被定义为泛型函数,能够适应多种数据类型。

在f1函数中,两个参数的类型必须相同,且返回值类型为数组,数组元素类型与输入参数类型一致。

function f1<T>(x: T, y: T): T[] {
    return [x, y]
}
 
const a = f1(123, 456)
const b = f1('123', '456')

在f2函数中,两个参数类型可以不同,返回值类型为对象,其中x属性类型与参数x相同,y属性类型与参数y相同。

function f2<T, U>(x: T,  y: U): { x: T, y: U} {
    return { x, y }
}
 
const a = f2('123', 456)
const b = f2(123, '456')

f3函数接收两个参数:参数a为任意类型的数组,参数f为处理a中元素的函数(返回任意类型),f3的返回值类型为f返回值的数组。

这段代码来源于直接粘贴,可能存在上下文缺失或理解上的困惑,需结合具体场景分析。

function f3<T, U>(a: T[], f: (x: T) => U): U[] {
    return a.map(f);
}
 
// f3<number, boolean> 约束T为number类型  U为boolean类型
const a: boolean[] = f3<number, boolean>([1, 2, 3], n => !! n)

泛型函数的类型推断机制允许编译器根据传入值自动推导类型参数,无需显式指定。

function f0<T>(x: T): T {
    return x
}
 
const a = f0(123) // 推断出类型为 123
const b = f0('123') // 推断出类型为 '123'

在此例中,编译器推断出的不是number和string类型,而是字符串字面量类型123和“123”。TS原则是将字面量视为字面量类型,仅在必要时放宽为基础类型。字符串字面量“123”比string更精确。

若类型参数仅在函数签名中出现一次且与其他值无关联,则无需使用类型参数,直接声明实际类型即可。

几乎任何函数都可声明为泛型函数,但若类型参数不表示参数间或参数与返回值间的关系,使用泛型可能构成反模式。

// 没必要使用泛型
function f<T>(x: T): void {
    console.log(x)
}
 
// 直接限定就好了
function f(x: number): void {
    console.log(x)
}

补充说明泛型的典型应用场景,帮助开发者在适当时候选择使用泛型。

在编写TypeScript时,若定义函数、接口或类时不预先指定具体类型,而是在使用时指定,即可使用泛型特性。

灵活使用泛型定义类型是掌握TypeScript的关键步骤,有助于提升代码的通用性和可维护性。

<泛型变量名称>(参数1: 泛型变量, 参数2: 泛型变量, ...参数n: 泛型变量) => 泛型变量

 /*------------基础使用方法------------*/
  function join<T, P>(first: T, second: P): T {
    return first;
  }
  //const twoParms = join<number, string>(1, '我是string');
  const twoParms = join(1, '我是string');

  /*---------泛型集合--------------*/
  function map<T>(params: Array<T>) {
    return params;
  }
  //const sanleType = map<string>(['123']);
  const sanleType = map(['123']);

  /* -----------泛型箭头函数-------------*/
  const identity = <T,>(arg: T): T => {
    return arg;
  };
  const identity2: <T>(arg: T) => T = (arg) => {
    return arg;
  };

泛型接口允许在接口定义中使用类型参数,使接口的属性或方法能够适应多种数据类型。

 /* -------------泛型接口-------------*/
 interface ColumnProps<T> {
  key: number | string;
  title: string;
  dataIndex: keyof T; // 约束 dataIndex 值需为引用泛型 T 中的属性
}
interface ITableItem {
  key: number | string;
  name: string;
  address: string;
  age: number;
}
const columns: Array<ColumnProps<ITableItem>> = [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name',
    },
  ];

泛型类允许在类定义中使用类型参数,使类的属性和方法能够处理多种数据类型。

 /*--------------泛型类---------------*/
  class Person<T> {
    love: T;
    say: (arg: T) => T;
  }
  let myFn: IGeneric<number> = fn;
  myFn(13); //13
 
  let me = new Person<string>();
  me.love = 'TS';
  // me.love = 520; // ERROR
  me.say = function(love: string){
    return `my love is ${love}`;
  }

泛型约束可以通过extends关键字实现,限制类型参数必须满足特定接口或类型要求。

泛型可以通过extends一个接口来实现泛型约束,确保类型参数符合特定结构。

<泛型变量 extends 接口>

<T, K extends keyof T>
//K为传入的T上已知的属性,

interface IArray {
  length: number
}
function logIndex<T extends IArray>(arg: T): void {
  for (let i = 0; i < arg.length; ++i) {
    console.log(i)
  }
}
let arr = [1, 2, 3]
// logIndex<number>(arr) // 报错
logIndex<number[]>(arr) // 允许
logIndex(arr) // 自动类型推导,允许

泛型在特定场景下能显著简化代码结构,例如处理动态数据类型时。

/*-------------应用场景start---------------------------*/
interface ColumnProps<T> {
  key: number | string;
  title: string;
  dataIndex: keyof T; // 约束 dataIndex 值需为引用泛型 T 中的属性
}
interface ITableItem {
  key: number | string;
  name: string;
  address: string;
  age: number;
}
interface TableProps {
  dataSource: ITableItem[];
  columns: Array<ColumnProps<ITableItem>>;
}
const MyTable = (props: TableProps) => {
  const { dataSource, columns } = props;
  return <Table dataSource={dataSource} columns={columns} />;
};
const ApplcationMod = () => {
  const dataSource = [
    {
      key: '1',
      name: '金城武',
      age: 32,
      address: '西湖区湖底公园1号',
    },
    {
      key: '2',
      name: '吴彦祖',
      age: 42,
      address: '西湖区湖底公园1号',
    },
  ];

  const columns: Array<ColumnProps<ITableItem>> = [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name',
    },
    {
      title: '年龄',
      dataIndex: 'age',
      key: 'age',
    },
    {
      title: '住址',
      dataIndex: 'address',
      key: 'address',
    },
  ];

  return (
    <div>
      <h3>泛型应用场景</h3>
      <MyTable dataSource={dataSource} columns={columns} />
    </div>
  );
};

总结全文要点,回顾泛型的核心概念与应用技巧。

到此这篇关于TypeScript中泛型使用的文章就介绍到这了,更多相关TypeScript泛型的使用内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章