在 TypeScript 中,泛型是一种在定义函数、类、接口等时,不预先指定具体的数据类型,而是在使用时再指定的机制。
注意:无法创建泛型枚举和泛型命名空间。
创建一个identity
函数, 这个函数会返回任何传入它的值。
function identify(arg) {
return arg
}
TypeScript 编译器会自动推断arg
的类型是any
。
使用any
类型会导致这个函数可以接收任何类型的arg
参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。
如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。
比如:
function identify(arg: any): any {
return `${arg}`
}
console.log( identify('aa') ); // a
console.log( identify(123) ); // 123
console.log( identify({"name": "张三"}) ); // [object Object]
因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了 类型变量,它是一种特殊的变量,只用于表示类型而不是值。
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("Hello");
let output2 = identity("Hello");
identity
函数是一个泛型函数,T
是一个类型参数。
T
捕获用户传入的类型。使用T
作为参数类型 和 返回值类型。这样,参数类型与返回值类型是相同的,允许跟踪函数里使用的类型的信息。
identity<string>("Hello")
明确的指定了T
是string
类型,并做为一个参数传给函数,使用了<>
括起来而不是()
。传入string
类型并返回string
类型。
identity("Hello")
没有明确指定类型。编译器会根据传入的参数自动地确定T
的类型(类型推论)。
identity
函数叫做泛型,适用于多个类型,它不会丢失信息,保持类型的准确性,传入的参数类型与 返回数值类型 相同。
使用泛型变量
以 identity
函数为例,它只是简单地返回传入的参数,因为泛型 T
可以是任意类型,所以不能随意假设它具有某些特定的属性或方法,比如 length
。
如果操作的 T
类型是数字,数字是没有 .length
属性的:
function identity<T>(arg: T): T {
console.log(arg.length); // Error: Property 'length' does not exist on type 'T'.
return arg;
}
let output1 = identity<number>(123);
如果操作的是 T
类型的数组(T[]
或 Array<T>
),因为数组是有 length
属性的,所以这样的操作就是合法且类型安全的。
function identity<T>(arr: T[]): void {
console.log(`数组长度: ${arr.length}`);
}
identity<string>(["a", "b", "c"]); // 数组长度: 3
identity<number>([1, 2, 3, 5]); // 数组长度: 4
identity
函数可以正确地处理不同类型的数组,并且由于操作的是数组,使用 length
属性是合法且类型安全的。
泛型函数的类型
泛型函数的类型可以通过类型参数和函数参数及返回值的类型来描述。
示例:
function identity<T>(arg: T): T {
return arg;
}
// 类型表示
let myIdentity: <T>(arg: T) => T = identity;
// 调用
let result1 = myIdentity<number>(5);
let result2 = myIdentity<string>("Hello");
myIdentity
的类型被声明为 <T>(arg: T) => T
,表示这是一个接受一个类型为 T
的参数并返回类型为 T
的结果的泛型函数类型。
let myIdentity: <T>(arg: T) => T = identity;
是在为变量 myIdentity
进行类型声明和赋值。
具体解释如下:
let myIdentity
:声明一个名为myIdentity
的变量。<T>(arg: T) => T
:这是函数类型的描述。其中<T>
表示这是一个泛型函数,T
是类型参数。(arg: T)
表示函数接受一个类型为T
的参数arg
,=> T
表示函数的返回值类型也是T
。= identity
:将已经定义的泛型函数identity
赋值给变量myIdentity
。
总的来说,这行代码表明myIdentity
是一个与identity
函数具有相同类型签名(接受一个泛型参数并返回相同类型的值)的变量,并将identity
函数的值赋给了它。
可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以:
// 类型表示
let myIdentity: <U>(arg: U) => U = identity;
可以使用带有调用签名的对象字面量来定义泛型函数:
function identity<T>(arg: T): T {
return arg;
}
// 类型表示
let myIdentity: {<T>(arg: T): T} = identity;
my
myIdentity
的类型被描述为 {<T>(arg: T): T}
,表示这是一个接受一个泛型参数 arg
并返回相同类型的函数。
泛型函数可以有多个参数:
function combine<T>(item1: T, item2: T): T[] {
return [item1, item2];
}
// 类型表示
let myCombine: <T>(item1: T, item2: T) => T[] = combine;
console.log( myCombine(10, 15) );
console.log( myCombine("hello", "world") );
myCombine
的类型被声明为 <T>(item1: T, item2: T): T[]
,表示接受两个类型为 T
的参数,并返回一个类型为 T
的数组。
注意: item1
的类型必须与 item2
的类型一致。
假如有一个泛型函数接受两个参数,一个是泛型类型,另一个是固定类型:
function combineData<T>(data: T, fixedValue: string): T {
// 在进行操作前,必须先进行类型断言或类型缩小的检查
if (typeof data === 'string') {
return (data + fixedValue) as T;
} else if (typeof data === 'number') {
return Number(data.toString() + fixedValue) as T;
} else {
throw new Error('不支持的类型');
}
}
// 类型表示
let myCombinedData: <T>(data: T, fixedValue: string) => T = combineData;
// 调用示例
let result1 = combineData<number>(10, '5'); // 105
let result2 = combineData<string>('Hello', ' World'); // "Hello World"
在这个示例中,第一个参数是泛型类型,第二个参数是固定类型,两个参数类型不同,在进行操作前,必须先进行类型断言或类型缩小的检查。
否则,会报错:参数类型不兼容。
可以使用泛型函数生成元组数据:
function createTuple<A, B>(first: A, second: B): [A, B] {
return [first, second];
}
// 类型表示
let CreateTupleFunction: <A, B>(first: A, second: B) => [A, B] = createTuple;
// 调用示例
let tuple1 = CreateTupleFunction<number, string>(10, "Hello");
console.log(tuple1); // [10, "Hello"]
// 也可以直接调用方法:
let tuple2 = createTuple<string, boolean>("World", true);
console.log(tuple2); // ["World", true]
泛型类
泛型类可以在创建类时不指定某些类型,而是在实例化类的时候再确定。
class GenericBox<T> {
private content: T;
constructor(value: T) {
this.content = value;
}
getContent(): T {
return this.content;
}
setContent(value: T) {
this.content = value;
}
}
// 使用示例
let box1 = new GenericBox<number>(10); // 类型是number,后续操作参数只能是number
console.log(box1.getContent()); // 输出: 10
let box2 = new GenericBox<string>("Hello");
console.log(box2.getContent()); // 输出: "Hello"
GenericBox
是一个泛型类,T
是类型参数。在创建类的实例时,指定具体的类型(如 number
或 string
)。
let box1 = new GenericBox<number>(10);
指定实例 box1
的类型是 number
,类型不可以改变。
泛型接口
可以定义具有泛型类型参数的接口。
示例:
interface KeyValuePair<K, V> {
key: K;
value: V;
}
// 使用示例
let stringNumberPair: KeyValuePair<string, number> = {
key: "id",
value: 123
};
let booleanStringPair: KeyValuePair<boolean, string> = {
key: true,
value: "active"
};
在上述示例中,KeyValuePair
是一个泛型接口,K
表示 key
的类型,V
表示 value
的类型。
再看一个更复杂的泛型接口示例,比如一个用于操作数组的接口:
interface ArrayOperations<T> {
push(item: T): void;
pop(): T | undefined;
getAtIndex(index: number): T | undefined;
}
class MyArray<T> implements ArrayOperations<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
getAtIndex(index: number): T | undefined {
return this.items[index];
}
}
let numberArray = new MyArray<number>();
numberArray.push(10);
numberArray.push(20);
let stringArray = new MyArray<string>();
stringArray.push("Hello");
stringArray.push("World");
在这个示例中,ArrayOperations
是一个泛型接口,定义了对数组的一些常见操作。MyArray
类实现了这个接口,用于处理特定类型的数组。
泛型约束
在 TypeScript 中,泛型约束用于对泛型类型参数施加一定的条件限制,以确保在使用泛型时具有某些特定的属性或方法。
创建一个包含 .length
属性的接口,使用这个接口和extends关键字来实现约束:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
let myString = loggingIdentity('hello'); // 正确,'string' 具有 'length' 属性
let myArray = loggingIdentity([1, 2, 3]); // 正确,数组具有 'length' 属性
let myObj = loggingIdentity({length: 10, value: 3}); // 正确,具有 'length' 属性
// 以下会报错,因为数字类型没有 'length' 属性
// Error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
let myNumber = loggingIdentity(5);
在泛型约束中使用类型参数
// 接口定义了一个对象类型,属性名的类型是string,属性值的类型由类型参数 T 决定。
interface Person<T> {
[key: string]: T;
}
// 将 ObjType 约束为 Person 接口类型
// 需要明确 Person 类的值的类型(明确T),设为any
// 不知道返回值的类型,也定为 any
function getProperty<ObjType extends Person<any>>(
obj: ObjType,
propName: keyof ObjType & string
): any {
if (propName in obj) {
return obj[propName];
} else {
throw new Error(`属性 '${propName}' 不在对象中`);
}
}
// 将 person 的类型指定为 Person<string> ,表示 Person 接口中的属性值都是字符串类型。
let person: Person<string> = {
name: "Alice",
likes: "dog"
};
console.log( getProperty(person,'name') );
// 将 person1 的类型指定为 Person<number> ,表示 Person 接口中的属性值都是number类型。
let person1: Person<number> = {
age: 25,
salary: 10000
};
console.log( getProperty(person1,'salary') );
在泛型中使用类类型
在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型。
示例:
class Vehicle {
wheels: number;
constructor(wheels: number) {
this.wheels = wheels;
}
}
class Car extends Vehicle {
color: string;
constructor(wheels: number, color: string) {
super(wheels);
this.color = color;
}
}
class Motorcycle extends Vehicle {
engineCapacity: number;
constructor(wheels: number, engineCapacity: number) {
super(wheels);
this.engineCapacity = engineCapacity;
}
}
// 构造函数
function createVehicle<T extends Vehicle>(ctor: new (arg: any) => T, arg: any): T {
return new ctor(arg);
}
// 创建汽车实例
let myCar = createVehicle(Car, { wheels: 4, color: 'red' });
console.log(myCar.color);
// 创建摩托车实例
let myMotorcycle = createVehicle(motorcycle, { wheels: 2, engineCapacity: 500 });
console.log(myMotorcycle.engineCapacity);
- 定义了
Vehicle
基类以及Car
和Motorcycle
派生类。 createVehicle
是一个泛型工厂函数,它接受一个类的构造函数ctor
和参数arg
,用于创建指定类型的实例:<T extends Vehicle>
:这是泛型部分,表示T
是一个类型参数,并且它必须是Vehicle
类或继承自Vehicle
类的类型。ctor: new (arg: any) => T
:这是函数的第一个参数。它是一个构造函数,接受一个类型为any
的参数arg
,并且这个构造函数创建的对象类型是由泛型T
确定的。arg: any
:这是函数的第二个参数,类型为any
,将作为第一个参数(构造函数)的输入。: T
:这指定了函数的返回值类型为泛型T
,即通过传入的构造函数创建并返回指定类型的对象。return new ctor(arg);
:创建一个(作为参数传入的) 类的实例。