typescript泛型接口-typeScript(子类别)

2023-09-28 0 8,906 百度已收录

介绍

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

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

子类HelloWorld

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

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

function identity(arg: number): number {
    return arg;
}
// 或者,我们使用any类型来定义函数:
function identity(arg: any): any {
    return arg;
}

使用任何类型都会导致该函数接收任何类型的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 是 string 类型,并将其作为参数传递给函数,使用括号而不是 ()。

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

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

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

使用子类变量

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

看看之前的身份示例:

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

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

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

typescript泛型接口-typeScript(子类别)

如果我们这样做,编译器会报错说我们使用了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。 是一个元素类型为T的字段typescript泛型接口,返回一个元素类型为T的字段。如果我们传入的是数值型字段,那么就会返回一个数值型字段,因为此时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; };
console.log(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;
}

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

因此,我们定义一个socket来描述约束。 创建一个包含 .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;
}

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

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(子类别) https://www.wkzy.net/game/197845.html

常见问题

相关文章

官方客服团队

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