TypeScript泛型的使用详细介绍

情景再现

这里针对一种情况,也是非常常见的一种情况:那就是

function identity(arg: number): number {
    return arg;
}

就是我接收一个number类型的参数,同时也返回一个number,那如果现在我想要接收一个string类型,同时也返回一个string,那么我就要再写一个函数像这样:

function identity2(arg: string): string{
    return arg;
}

那如果我现在想要void类型…

可能大家会想,那全部都变成any不就行了?像下面这样

function identity(arg: any): any {
    return arg;
}

使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。 如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。

那这样不就与我们一开始的设想不一致了吗?我传入Number,返回string,也不会报错呀!

因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了类型变量,它是一种特殊的变量,只用于表示类型而不是值。

function identity(arg: T): T {
    return arg;
}

我们给identity添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。

‍我们把这个版本的identity函数叫做泛型,因为它可以适用于多个类型。 不同于使用any,它不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型。

使用泛型

第一种是,传入所有的参数,包含类型参数

TypeScript泛型的使用详细介绍_第1张图片

第二种方法更普遍。利用了类型推论 – 即编译器会根据传入的参数自动地帮助我们确定T的类型

function identity(arg: T): T {
    return arg;
}
let output = identity("myString");
let output2 = identity("myString2"); 
console.log(output);
console.log(output2);

TypeScript泛型的使用详细介绍_第2张图片

注意我们没必要使用尖括号(<>)来明确地传入类型;编译器可以查看myString的值,然后把T设置为它的类型。 类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的。

泛型类型

我们研究一下函数本身的类型,以及如何创建泛型接口。

来看看泛型类型不同的展现方式:

function identity(arg: T): T {
    return arg;
}
let myFunction: (arg:T) => T = identity;

我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。

function identity(arg: T): T {
    return arg;
}
let myFunction: (arg:T) => T = identity;
let myIdentity: (arg: U) => U = identity;

我们还可以使用带有调用签名的对象字面量来定义泛型函数:

function identity(arg: T): T {
    return arg;
}
let myIdentity: {(arg: T): T} = identity;

是不是花了眼哈哈哈哈哈

泛型接口

还是以上面的为例子噢

interface GenericIdentityFn {
    (arg: T): T;
}
function identity(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn = identity;

可不可以详细一点

一个相似的例子,我们可能想把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型(比如:Dictionary< string>而不只是Dictionary)。 这样接口里的其它成员也能知道这个参数的类型了。

interface GenericIdentityFn {
    (arg: T): T;
}
function identity(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn = identity;

泛型类

我等这个泛型类等了好久好久

泛型类看上去与泛型接口差不多。 泛型类使用(<>)括起泛型类型,跟在类名后面。

// 泛型类
class GenericNumber {
    zeroValue: T | undefined;
    add: ((x: T, y: T) => T) | undefined;
}
let myGenericNumber = new GenericNumber();
myGenericNumber.zeroValue = 0;
function myAdd(x:number, y:number) { 
    return x + y; 
};
myGenericNumber.add = function(x, y) { 
    return x + y; 
};
console.log(myGenericNumber.add(1,2));

TypeScript泛型的使用详细介绍_第3张图片

换string玩玩

// 泛型类
class GenericNumber {
    zeroValue: T | undefined;
    add: ((x: T, y: T) => T) | undefined;
}
let stringNumeric = new GenericNumber();
stringNumeric.zeroValue = "hi,";
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));

TypeScript泛型的使用详细介绍_第4张图片

也不是全能的

与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。

注意点❗❗❗❗❗❗类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。

泛型约束

到底有没有长度啊,救命

当我们使用泛型的时候,有这种情况:

我想要打印出传过来的参数的长度为多少

function loggingIdentity(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

这里会扯到一个问题,首先,你传过来的这个玩意儿,它本身有长度吗??

首先,什么样的类型会有长度,毫无疑问,数组嘛

那我如果传入的不是数组,那就铁定报错,就像上面那样,正确的写法大家也都懂:

function loggingIdentity(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

相比于操作any所有类型,我们想要限制函数去处理任意带有.length属性的所有类型。 只要传入的类型有这个属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于T的约束要求。

interface Lengthwise {
    length: number;
}
function loggingIdentity(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

现在传入一个数字试试

TypeScript泛型的使用详细介绍_第5张图片

传入数组:

我们需要传入符合约束类型的值,必须包含必须的属性:

TypeScript泛型的使用详细介绍_第6张图片

在泛型里使用类类型[]

在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型。

跟在Java中的很像——工厂模式,很是高级

function create(c: {new(): T; }): T {
    return new c();
}

高级案例

应用场景:传入这个类,自动创建该类并且返回相应的属性。

class BeeKeeper {
    hasMask: boolean = false;
}
class ZooKeeper {
    nametag:string =  "ZooKeeper.nametag";
}
class Animal {
    numLegs: number = 100;
}
class Bee extends Animal {
    keeper: BeeKeeper = new BeeKeeper();
}
class Lion extends Animal {
    keeper: ZooKeeper = new ZooKeeper();
}
function createInstance(c: new () => A): A {
    return new c();
}
console.log(createInstance(Lion).keeper.nametag); 
console.log(createInstance(Bee).keeper.hasMask);

TypeScript泛型的使用详细介绍_第7张图片

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

你可能感兴趣的:(TypeScript泛型的使用详细介绍)