TS泛型
泛型
泛型写法
泛型主要用在四个场合:函数、接口、类和别名。
函数上的泛型
function 关键字定义的泛型函数,类型参数放在尖括号中,写在函数名后面。
1
2
3
function id<T>(arg:T):T {
return arg;
}
于变量形式定义的函数,泛型有下面两种写法:
1
2
3
4
5
// 写法一
let myId:<T>(arg:T) => T = id;
// 写法二
let myId:{ <T>(arg:T): T } = id;
接口的泛型
1
2
3
4
5
interface Box<Type> {
contents: Type;
}
let box:Box<string>;
泛型接口还有第二种写法。
1
2
3
4
5
6
7
8
9
interface Fn {
<Type>(arg:Type): Type;
}
function id<Type>(arg:Type): Type {
return arg;
}
let myId:Fn = id;
第二种写法还有一个差异之处。那就是它的类型参数定义在某个方法之中,其他属性和方法不能使用该类型参数。前面的第一种写法,类型参数定义在整个接口,接口内部的所有属性和方法都可以使用该类型参数。
类的泛型
泛型类的类型参数写在类名后面
1
2
3
4
class Pair<K, V> {
key: K;
value: V;
}
泛型也可以用在类表达式:
1
2
3
4
5
6
const Container = class<T> {
constructor(private readonly data:T) {}
};
const a = new Container<boolean>(true);
const b = new Container<number>(0);
JavaScript 的类本质上是一个构造函数,因此也可以把泛型类写成构造函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type MyClass<T> = new (...args: any[]) => T;
// 或者
interface MyClass<T> {
new(...args: any[]): T;
}
// 用法实例
function createInstance<T>(
AnyClass: MyClass<T>,
...args: any[]
):T {
return new AnyClass(...args);
}
类型别名的泛型写法
type 命令定义的类型别名,也可以使用泛型。
1
type Nullable<T> = T | undefined | null;
Nullable<T>
是一个泛型,只要传入一个类型,就可以得到这个类型与 undefined 和 null 的一个联合类型
1
2
3
4
5
6
7
8
9
10
11
type Container<T> = { value: T };
const a: Container<number> = { value: 0 };
const b: Container<string> = { value: 'b' };
// 定义树形结构的例子
type Tree<T> = {
value: T;
left: Tree<T> | null;
right: Tree<T> | null;
};
类型参数的默认值
类型参数可以设置默认值。使用时,如果没有给出类型参数的值,就会使用默认值。
1
2
3
4
5
function getFirst<T = string>(
arr:T[]
):T {
return arr[0];
}
类型参数的默认值,往往用在类中。
1
2
3
4
5
6
7
class Generic<T = string> {
list:T[] = []
add(t:T) {
this.list.push(t)
}
}
一旦类型参数有默认值,就表示它是可选参数。如果有多个类型参数,可选参数必须在必选参数之后。
1
2
3
<T = boolean, U> // 错误
<T, U = boolean> // 正确
数组的泛型表示
数组类型有一种表示方法是 Array<T>
。这就是泛型的写法,Array 是 TypeScript 原生的一个类型接口,T 是它的类型参数。声明数组时,需要提供 T 的值。
1
let arr:Array<number> = [1, 2, 3];
TypeScript 默认还提供一个 ReadonlyArray<T>
接口,表示只读数组。
1
2
3
4
5
function doStuff(
values:ReadonlyArray<string>
) {
values.push('hello!'); // 报错
}
参数 values 的类型是
ReadonlyArray<string>
,表示不能修改这个数组,所以函数体内部新增数组成员就会报错。因此,如果不希望函数内部改动参数数组,就可以将该参数数组声明为ReadonlyArray<T>
类型。
类型参数的约束条件
TypeScript 提供了一种语法,允许在类型参数上面写明约束条件,如果不满足条件,编译时就会报错。这样也可以有良好的语义,对类型参数进行说明。
1
2
3
4
5
6
7
8
9
10
11
12
13
function comp<T extends { length: number }>(
a: T,
b: T
) {
if (a.length >= b.length) {
return a;
}
return b;
}
comp([1, 2], [1, 2, 3]) // 正确
comp('ab', 'abc') // 正确
comp(1, 2) // 报错
T extends { length: number }
就是约束条件,表示类型参数 T 必须满足{ length: number }
,否则就会报错。
类型参数的约束条件采用下面的形式:,TypeParameter
表示类型参数,extends
是关键字,这是必须的,ConstraintType
表示类型参数要满足的条件,即类型参数应该是 ConstraintType 的子类型。
1
<TypeParameter extends ConstraintType>
类型参数可以同时设置约束条件和默认值,前提是默认值必须满足约束条件。
1
2
3
4
type Fn<A extends string, B extends string = 'world'>
= [A, B];
type Result = Fn<'hello'> // ["hello", "world"]
如果有多个类型参数,一个类型参数的约束条件,可以引用其他参数。
1
2
3
<T, U extends T>
// 或者
<T extends U, U>
但是,约束条件不能引用类型参数自身。
1
2
<T extends T> // 报错
<T extends U, U extends T> // 报错