typescript数组定义-TypeScript 泛型(generic)详解

2023-08-29 0 5,310 百度已收录

介绍

在软件工程中,除了创建一致且定义良好的API之外,我们还必须考虑可重用性。 组件除了支持当前的数据类型外,还可以支持未来的数据类型,这为您在创建小型系统时提供了非常灵活的功能。

在C#、Java等语言中,可以使用子类来创建可重用的组件,并且一个组件可以支持多种类型的数据。 这允许用户使用具有自己的数据类型的组件。

通用你好世界

让我们创建第一个使用子类的示例:恒等函数。 该函数返回传递给它的任何值。 您可以将此功能视为 echo 命令。

如果没有类库,该函数可能如下所示:

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

或者,我们使用 any 类型来定义函数:

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

使用any类型会导致该函数接受任意类型的arg参数,因此会丢失一些信息:传入的类型和返回的类型应该相同。 如果我们传入一个数字,我们就知道可以返回任何类型的值。

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

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

我们为identity添加了一个类型变量T。 T帮助我们捕获用户传入的类型(例如:number),然后我们就可以使用这个类型。 然后我们再次使用 T 作为返回类型。 现在我们可以知道参数类型与返回值类型相同。 这使我们能够跟踪有关函数中使用的类型的信息。

我们将这个版本的恒等函数称为类库,因为它适用于多种类型。 与使用any不同,它不会丢失信息,像第一种情况一样保持精确性,传入数字类型并返回数字类型。

当我们定义了一个子类函数之后,它可以通过两种方式使用。 首先是传入所有参数,包括类型参数:

let output = identity("myString");  // type of output will be 'string'

这里我们明确指定T是字符串类型,并将其作为参数传递给函数,使用括号而不是()。

第二种方式比较常见。 使用的是类型推断——即编译器会根据传入的参数手动帮我们判断T的类型:

let output = identity("myString");  // type of output will be 'string'

请注意,我们不必使用尖括号 ( ) 来显式传递类型; 编译器可以查看 myString 的值并将 T 设置为其类型。 类型推断帮助我们保持代码的简洁性和高度可读性。 如果编译器无法手动推导类型,则只能像以前一样显式传入 T 的类型。 在一些复杂的情况下,可能会出现这种情况。

使用子类变量

当使用子类创建像identity这样的类库函数时,编译器要求你在函数体中正确使用这个泛型类型。 换句话说,您必须将这些参数视为任何或所有类型。

看前面的身份示例:

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

typescript数组定义-TypeScript 泛型(generic)详解

如果我们想同时复制arg的厚度。 我们很可能会这样做:

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

如果我们这样做,编译器会抱怨我们使用了 arg 的 .length 属性,但没有地方说 arg 具有此属性。 请记住,这些类型变量代表任意类型,因此使用此函数的人可能会传入数字,而数字没有 .length 属性。

现在假设我们想要操作 T 类型的字段而不是直接操作 T。 由于我们操作的是链表,因此 .length 属性应该存在。 我们可以像任何其他链表一样创建这个字段:

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

你可以这样理解loggingIdentity的类型:泛型函数loggingIdentity接收类型参数T和参数arg,参数arg是一个元素类型为T的字段,并返回一个元素类型为T的字段。如果我们传入一个number 字段,会返回一个 number 字段,因为此时 T 是 number 类型。 这允许我们使用基类变量 T 作为类型的一部分,而不是整个类型,从而增加了灵活性。

我们还可以像这样实现里面的情况:

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

如果您使用过其他语言,您可能已经熟悉这些句型。在下一节中,我们将介绍如何创建像 Array 这样的自定义子类

泛型

在上一节中,我们创建了恒等泛型函数,它可以应用于不同的类型。 在本节中,我们将了解函数本身的类型,以及如何创建库套接字。

泛型函数的类型与非泛型函数的类型没有什么不同,只是顶部有一个类型参数,就像函数声明一样:

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

我们也可以使用不同的类库参数名称,只要数量和用途对应即可。

function identity(arg: T): T {
    return arg;
}
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而不仅仅是Dictionary)。 这样,socket中的其他成员也可以知道这个参数的类型。

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

请注意,我们的示例略有变化。 不再描述泛型函数,但非泛型函数签名作为参数类型的一部分包含在内。 当我们使用GenericIdentityFn时,我们要传入一个类型参数来指定参数类型(这里是:number),这样就锁定了以后代码中使用的类型。 为了描述类型的哪些部分是子类的一部分,了解何时将参数放在调用签名中以及何时将它们放在套接字上会很有帮助。

除了类库套接字之外,我们还可以创建通用类。 请注意,无法创建类库枚举和基类命名空间。

泛型类

通用类看起来很像参数套接字。 泛型类使用 () 将子类类型括在类名旁边。

class GenericNumber {
    zeroValue: T;
    add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

GenericNumber 类的使用非常直观,您可能已经注意到没有什么限制它只能使用数字类型。 也可以使用字符串或其他更复杂的类型。

let stringNumeric = new GenericNumber();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
alert(stringNumeric.add(stringNumeric.zeroValue, "test"));

与套接字一样,将基类型直接放在类的前面有助于我们确保类的所有属性都使用相同的类型。

正如我们在类部分中所说,类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,因此类的静态属性不能使用该类库类型。

通用约束

大家应该还记得前面的一个例子,我们有时想要操作某种类型的一组值,并且我们知道这组值有什么属性。 以loggingIdentity为例,我们想要访问arg的length属性,但是编译器无法证明每种类型都有length属性,因此会报错。

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

我们不想对任何类型的所有类型进行操作typescript数组定义,而是希望将函数限制为具有 .length 属性的任何类型。 只要传入的类型有这个属性typescript数组定义,我们就允许它,也就是说至少包含这个属性。 为此,我们需要枚举 T 的约束。

为此,我们定义一个套接字来描述约束。 创建一个包含 .length 属性的套接字,并将该套接字与 extends 关键字一起使用来实现约束:

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数组定义-TypeScript 泛型(generic)详解

现在基类函数受到限制,因此它不再适用于任何类型:

loggingIdentity(3);  // Error, number doesn't have a .length property

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

loggingIdentity({length: 10, value: 3});

在子类约束中使用类型参数

您可以声明一个由另一个类型参数绑定的类型参数。 例如,现在我们想使用属性名称从对象中获取该属性。 而我们要确保这个属性存在于对象 obj 上,所以我们需要在这两种类型之间使用约束。

function getProperty(obj: T, key: K) {
    return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

在子类中使用类类型

当 TypeScript 使用子类创建鞋工厂函数时,需要引用构造函数的类类型。 例如,

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

一个更中间的示例使用原型属性来推断和约束构造函数和类实例之间的关系。

class BeeKeeper {
    hasMask: boolean;
}
class ZooKeeper {
    nametag: string;
}
class Animal {
    numLegs: number;
}
class Bee extends Animal {
    keeper: BeeKeeper;
}
class Lion extends Animal {
    keeper: ZooKeeper;
}
function createInstance(c: new () => A): A {
    return new c();
}
createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 typescript typescript数组定义-TypeScript 泛型(generic)详解 https://www.wkzy.net/game/170799.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务