外观
外观
怡然
1959字约7分钟
2024-11-06
function getFirst<T>(arr: T[]): T {
return arr[0];
}
上面示例中,函数
getFirst()
的函数名后面尖括号的部分<T>
,就是类型参数,参数要放在一对尖括号(<>
)里面。本例只有一个类型参数T
,可以将其理解为类型声明需要的变量,需要在调用时传入具体的参数类型。上例的函数
getFirst()
的参数类型是T[]
,返回值类型是T
,就清楚地表示了两者之间的关系。比如,输入的参数类型是number[]
,那么T
的值就是number
,因此返回值类型也是number
。
getFirst<number>([1, 2, 3]);
上面示例中,调用函数
getFirst()
时,需要在函数名后面使用尖括号,给出类型参数T的值,本例是<number>
。
TypeScript
自己推断。getFirst([1, 2, 3]);
TypeScript
可能推断不出类型参数的值,这时就必须显式给出了。function comb<T>(arr1: T[], arr2: T[]): T[] {
return arr1.concat(arr2);
}
comb([1, 2], ["a", "b"]); // 报错
comb<number | string>([1, 2], ["a", "b"]); // 正确
上面示例中,类型参数是一个联合类型,使得两个参数都符合类型参数,就不报错了。这种情况下,类型参数是不能省略不写的。
T
(type
的第一个字母)作为类型参数的名字。如果有多个类型参数,则使用 T
后面的 U
、V
等字母命名,各个参数之间使用逗号(“,
”)分隔。function map<T, U>(arr: T[], f: (arg: T) => U): U[] {
return arr.map(f);
}
// 用法实例
map<string, number>(["1", "2", "3"], (n) => parseInt(n)); // 返回 [1, 2, 3]
注意
总之,泛型可以理解成一段类型逻辑,需要类型参数来表达。有了类型参数以后,可以在输入类型与输出类型之间,建立一一对应关系。
function
关键字定义的函数写法:function id<T>(arg: T): T {
return arg;
}
// 写法一
let myId: <T>(arg: T) => T = id;
// 写法二
let myId: { <T>(arg: T): T } = id;
// 第一种写法
interface Box<Type> {
contents: Type;
}
let box: Box<string>;
// 第二种写法
interface Fn {
<Type>(arg: Type): Type;
}
function id<Type>(arg: Type): Type {
return arg;
}
let myId: Fn = id;
class Pair<K, V> {
key: K;
value: V;
}
class A<T> {
value: T;
}
class B extends A<any> {}
上面示例中,类
A
有一个类型参数T
,使用时必须给出T
的类型,所以类B继承时要写成A<any>
。
const Container = class<T> {
constructor(private readonly data: T) {}
};
const a = new Container<boolean>(true);
const b = new Container<number>(0);
JavaScript
的类本质上是一个构造函数,因此也可以把泛型类写成构造函数。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);
}
class C<T> {
static data: T; // 报错
constructor(public value: T) {}
}
type
命令定义的类型别名,也可以使用泛型。type Nullable<T> = T | undefined | null;
function getFirst<T = string>(arr: T[]): T {
return arr[0];
}
上面示例中,
T = string
表示类型参数的默认值是string
。调用getFirst()
时,如果不给出T
的值,TypeScript
就认为T
等于string
。
TypeScript
会从实际参数推断出T
的值,从而覆盖掉默认值,所以下面的代码不会报错。getFirst([1, 2, 3]); // 正确
class Generic<T = string> {
list: T[] = [];
add(t: T) {
this.list.push(t);
}
}
<T = boolean, U> // 错误
<T, U = boolean> // 正确
Array
是 TypeScript
原生的一个类型接口,T
是它的类型参数。声明数组时,需要提供T
的值。let arr: Array<number> = [1, 2, 3];
在 TypeScript
内部,数组类型的另一种写法number[]
、string[]
,只是Array<number>
、Array<string>
的简写形式。
在 TypeScript
内部,Array
是一个泛型接口,类型定义基本是下面的样子。
interface Array<Type> {
length: number;
pop(): Type | undefined;
push(...items: Type[]): number;
// ...
}
上面代码中,
push()
方法的参数item
的类型是Type[]
,跟Array()
的参数类型Type
保持一致,表示只能添加同类型的成员。调用push()
的时候,TypeScript
就会检查两者是否一致。
TypeScript
内部数据结构,比如Map
、Set
和Promise
,其实也是泛型接口,完整的写法是Map<K, V>
、Set<T>
和Promise<T>
。TypeScript
默认还提供一个ReadonlyArray<T>
接口,表示只读数组。function comp<Type>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
}
return b;
}
上面示例中,类型参数
Type
有一个隐藏的约束条件:它必须存在length
属性。如果不满足这个条件,就会报错。
TypeScript
提供了一种语法,允许在类型参数上面写明约束条件,如果不满足条件,编译时就会报错。这样也可以有良好的语义,对类型参数进行说明。function comp<T extends { length: number }>(a: T, b: T) {
if (a.length >= b.length) {
return a;
}
return b;
}
上面示例中,
T extends { length: number }
就是约束条件,表示类型参数T
必须满足{ length: number }
,否则就会报错。
TypeParameter
表示类型参数,extends
是关键字,这是必须的,ConstraintType
表示类型参数要满足的条件,即类型参数应该是ConstraintType
的子类型。<TypeParameter extends ConstraintType>
type Fn<A extends string, B extends string = "world"> = [A, B];
type Result = Fn<"hello">; // ["hello", "world"]
<T, U extends T>
// 或者
<T extends U, U>
<T extends T> // 报错
<T extends U, U extends T> // 报错
T
的约束条件是U
、U
的约束条件是T
),因为互相约束就意味着约束条件就是类型参数自身。