玩转TypeScript之泛型(中级版)

2023-05-16

是不是没想到在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<T>(arg: T): T {
    return arg;
}

我们给identity添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。
🐱‍🏍我们把这个版本的identity函数叫做泛型因为它可以适用于多个类型。 不同于使用any,它不会丢失信息,像第一个例子那像保持准确性,传入数值类型并返回数值类型。

使用泛型

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

在这里插入图片描述

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

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

let output = identity<string>("myString");
let output2 = identity("myString2"); 
console.log(output);
console.log(output2);

在这里插入图片描述

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

泛型类型

我们研究一下函数本身的类型,以及如何创建泛型接口。
来看看泛型类型不同的展现方式:

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

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

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

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

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

是不是花了眼哈哈哈哈哈

泛型接口

还是以上面的为例子噢

interface GenericIdentityFn {
    <T>(arg: T): T;
}

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

let myIdentity: GenericIdentityFn = identity;

可不可以详细一点

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

interface GenericIdentityFn<T> {
    (arg: T): T;
}

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

let myIdentity: GenericIdentityFn<number> = identity;

泛型类(大佬来了)

我等这个泛型类等了好久好久🐫🐫🐫
泛型类看上去与泛型接口差不多。 泛型类使用(<>)括起泛型类型,跟在类名后面。

// 泛型类
class GenericNumber<T> {
    zeroValue: T | undefined;
    add: ((x: T, y: T) => T) | undefined;
}
let myGenericNumber = new GenericNumber<number>();
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));

在这里插入图片描述

换string玩玩

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

在这里插入图片描述

也不是全能的

与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。
注意点❗❗❗❗❗❗类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型

泛型约束

到底有没有长度啊,救命

当我们使用泛型的时候,有这种情况:
我想要打印出传过来的参数的长度为多少

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

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

首先,什么样的类型会有长度,毫无疑问,数组嘛
那我如果传入的不是数组,那就铁定报错,就像上面那样,正确的写法大家也都懂:

function loggingIdentity<T>(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<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

现在传入一个数字试试
在这里插入图片描述

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

在这里插入图片描述

在泛型里使用类类型[]

在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型。
跟在Java中的很像——工厂模式,很是高级

function create<T>(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<A extends Animal>(c: new () => A): A {
    return new c();
}

console.log(createInstance(Lion).keeper.nametag); 
console.log(createInstance(Bee).keeper.hasMask);

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

玩转TypeScript之泛型(中级版) 的相关文章

随机推荐