Skip to content
On this page

泛型的类 / 接口 / 函数

背景:很多时候,我们希望一个函数或者类可以支持多种数据类型 栗子, 一个打印函数的改造:函数会返回任何传入它的值

typescript
function log(value: string): string {
  console.log(value);
  return value;
}
1
2
3
4

改造为接受一个数组参数

方式一:函数重载

typescript
function log(value: string): string;

function log(value: string[]): string[];

function log(value: any) {
  console.log(value);
  return value;
}
1
2
3
4
5
6
7
8

方式二:联合类型

typescript
function log(value: string | string[]): string | string[] {
  return value;
}
1
2
3

希望 log 函数可以接受任意类型的参数

方式一:any 类型

但是,丢失了类型之间的约束关系,忽略了参数类型与函数的返回值类型必须是一致的

typescript
function log(value: any): any {
  console.log(value);
  return value;
}
1
2
3
4

方式二:泛型

  1. 通过泛型来定义函数 - 泛型函数

概念:不预先确定数据类型,具体的类型在使用的时候才能确认

typescript
function log<T>(value: T): T {
  return value;
}

//调用方式一: 在调用的时候 指定 T的类型
console.log(
  log<string[]>(["a", "b"]),
);

// 调用方式二:  利用TS的类型推断,省略 函数的参数类型 --- 推荐
console.log(log(["a", "b"]));
1
2
3
4
5
6
7
8
9
10
11
  1. 通过泛型来定义函数类型 -- 泛型函数类型

泛型可以定义一个函数,也可以定义一个函数类型

typescript
function log<T>(value: T): T {
  return value;
}

/*
类型别名-------------

使用类型别名定义一个泛型函数类型,
等号后面跟函数签名差不多, 但是要把函数的名称去掉
*/
type Log = <T>(value: T) => T;

// 泛型函数实现
let myLog: Log = log;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

typescript
function identity<T>(arg: T): T {
  return arg;
}

let myIdentity: <T>(arg: T) => T = identity;

let myIdentity1: <U>(arg: U) => U = identity;

1
2
3
4
5
6
7
8

泛型接口

在这里,泛型仅仅约束了一个函数,但我们也可以用泛型来约束接口的其他成员

typescript
// 和类型别名的方式是完全等价的
interface Log {
  <T>(value: T): T;
}
1
2
3
4

用泛型来约束接口的其他成员:把泛型放在接口名称的后面,这样接口的所有成员都能受到泛型的约束了

typescript
interface Log<T> {
  (value: T): T;
}
1
2
3

注:泛型变量约束了整个接口后,在实现的时候,必须指定一个类型

typescript
function log<T>(value: T): T {
  return value;
}

interface Log<T> {
  (value: T): T;
}

// 错误提示:  泛型类型“Log<T>”需要 1 个类型参数。ts(2314)
let myLog: Log = log;

// 解决
let myLog1: Log<number> = log;
// myLog1的参数只能是 number
myLog1(1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

也可以在接口的定义中指定一个默认类型

typescript
// 指定默认类型
interface Log<T = Array<string>> {
  (value: T): T;
}

let myLog: Log = log;

myLog(["1", "2"]);
1
2
3
4
5
6
7
8

泛型也可以约束类的成员

泛型类

把泛型变量放在类的名称后面,就可以约束所有类的成员了 注意:泛型不能约束类的静态成员

typescript
class Test<K> {
  // 错误提示:  静态成员不能引用类类型参数。ts(2302)
  // static eat(param:T){

  // }

  run(value: K) {
    return value;
  }
}
1
2
3
4
5
6
7
8
9
10

实例的方法将会受到泛型的约束

typescript
// 实例化
let log1 = new Test<number>();

// 错误提示: 类型“"a"”的参数不能赋给类型“number”的参数。ts(2345)
// log1.run('a')

log1.run(1);
1
2
3
4
5
6
7

实例化时可以不传入类型参数 当没有指定参数时, 实例方法的参数类型可以是任意的

typescript
let log2 = new Test();

log2.run("a");

log2.run(1);

1
2
3
4
5
6

泛型约束

typescript
function testA<T>(value: T): T {
  // 类型“T”上不存在属性“length”。ts(2339)
  console.log(value, value.length);

  return value;
}
1
2
3
4
5
6

解决:T 继承 Length 接口,表示 T 受到了约束,即输入的参数必须具有 length 属性

typescript
interface Length {
  length: number;
}

function testA<T extends Length>(value: T): T {
  console.log(value, value.length);

  return value;
}

testA([1]);

testA("12212");

testA({ length: 1 });

/*
错误提示:
类型“{ a: number; }”的参数不能赋给类型“Length”的参数。
  对象文字可以只指定已知属性,并且“a”不在类型“Length”中。ts(2345)
*/
// testA({a:1})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

泛型的好处

  • 函数和类可以轻松地支持多种类型,增强程序的扩展性
  • 不必写多条函数重载,冗长的二联合类型声明,增强代码可读性
  • 灵活控制类型之间的约束
沪ICP备20006251号-1