当前位置: 首页 > Web前端 > HTML

TypeScript官方手册翻译计划[6]-TypeManipulation-Generics

时间:2023-03-28 15:00:23 HTML

说明:目前网上还没有最新的TypeScript官方文档的中文翻译,所以才有这样的翻译计划。因为本人也是TypeScript的初学者,不能保证翻译100%准确。如有错误,请在评论区指出;翻译内容:暂定翻译内容为TypeScriptHandbook,其他部分翻译文档稍后补充;项目地址:TypeScript-Doc-Zh,如果对你有帮助,可以点个star~本章官方文档地址:Generics通用软件工程的一个主要部分是构建组件。组件不仅具有定义明确且一致的API,而且还可以重用。如果一个组件能够同时处理当前数据和未来数据,那么在构建大型软件系统时,它将为您提供最灵活的能力。在C#和Java等语言中,创建可重用组件的主要工具之一是泛型。使用泛型,我们可以创建适用于多种类型而不是单一类型的组件,允许用户在使用这些组件时使用自己的类型。泛型的初步认识泛型的初步认识,让我们先实现一个恒等函数。identity函数可以接受任意参数并返回它们,您可以将其视为类似于echo命令的功能。如果不使用泛型,那么我们必须为身份函数指定一个特定的类型:functionidentity(arg:number):number{returnarg;}或者,我们可以使用任何类型来描述身份函数:functionidentity(arg:any):any{returnarg;}使用any确实是一种常见的做法,因为函数的arg参数可以接受任何类型或所有类型的值,但实际上我们丢失了函数返回值类型的信息。如果我们传递的参数是一个数字,那么我们唯一能知道的信息就是该函数可以返回任何类型的值。相反,我们需要一种方法来捕获参数的类型,以便我们也可以使用它来表示返回值的类型。在这里,我们将使用一个类型变量,一个对类型而不是值进行操作的特殊变量。functionidentity(arg:Type):Type{returnarg;}我们现在已经将类型变量Type添加到身份函数中。Type可以让我们捕捉到用户传入的参数类型(比如数字类型),作为后面可以利用的信息。接下来,我们还在返回类型的声明中使用Type。从代码中可以看出,参数和返回值都使用相同的类型,这使得我们可以在函数的一侧接受类型信息,并将信息作为函数的输出传递到另一侧。我们将此版本的恒等函数称为泛型函数,因为它适用于一系列类型。与使用any不同,此函数非常具体(即,它不会丢失任何类型信息),并且与之前使用数字作为参数和返回类型的恒等函数具有相同的效果。一旦实现了通用身份函数,我们就可以通过两种方式调用它。第一种方式是将所有参数传递给函数,包括类型参数:letoutput=identity("myString");^^^^^//letoutput:string在这里,我们显式地将Type设置为string并将其设置为函数调用的参数。请注意,包装参数是<>而不是()。第二种方式可能是最常用的。我们在这里使用类型参数推断——也就是说,我们希望编译器根据传递的参数类型自动设置Type的值:letoutput=identity("myString");^^^^^//letoutput:string注意我们不必在<>中显式传入类型。编译器查看值myString并将该值的类型用作Type的值。虽然类型参数推断可以有效保证代码的简洁性和可读性,但在更复杂的情况下,编译器可能无法成功推断出类型。这时候就需要像之前一样显式传入类型参数。使用泛型类型变量在开始使用泛型之后,您会注意到每次创建像identity这样的泛型函数时,编译器都会强制您在函数体中正确使用任何泛型类型。类型参数。也就是说,它会认为您将这些参数视为任意的所有类型。看一下我们之前写的identity函数:functionidentity(arg:Type):Type{returnarg;}如果我们每次调用都想把参数arg的长度打印到控制台,what我们应该做什么?羊毛布?我们可能会尝试编写以下代码:functionloggingIdentity(arg:Type):Type{console.log(arg.length);^^^^^^//类型“Type”上不存在属性“length”。returnarg;}当我们这样做时,编译器会抛出一个错误,指出我们正在访问arg的.length成员,但是arg之前没有声明在任何地方拥有这个成员。记住我们之前说过的,这些类型变量代表任意类型和所有类型,所以使用这个函数的人可能会传入一个类型number的值,而这个值没有.length成员。假设我们实际上希望此函数在Type类型的数组上工作,而不是直接在Type上工作。由于我们正在处理一个数组,它必须有一个.length成员。我们可以像创建其他类型的数组一样描述它:functionloggingIdentity(arg:Type[]):Type[]{console.log(arg.length);returnarg;}你可以将loggingIdentity的类型解释为“通用函数loggingIdentity接受一个类型参数Type,以及一个参数arg,它是一个Type类型的数组。该函数最终返回一个Type类型的数组。”如果我们传入一个number类型的数组,最后返回的也是一个number类型的数组,因为Type会绑定number。这允许我们将泛型类型变量Type作为要使用的类型之一,而不是作为整个类型,从而为我们提供了更大的灵活性。我们也可以将这个简单的例子重写如下://Array有一个length属性,所以它不会Thenthrowanerrorreturnarg;}你可能已经熟悉其他语言中的这种类型风格。在下一节中,我们将向您展示如何自己创建像Array这样的泛型。通用类型在前面的部分中,我们创建了一个适用于一系列类型的通用函数标识。在本节中,我们将探讨函数本身的类型以及创建通用接口的方法。泛型函数类型与非泛型函数类型相同,只是前者会像泛型函数声明一样先列出类型参数:functionidentity(arg:Type):Type{returnarg;}letmyIdentity:(arg:Type)=>Type=identity;我们也可以为类型中的泛型类型参数使用不同的名称,只要类型变量的数量和用法保持不变即可:functionidentity(arg:Type):Type{returnarg;}letmyIdentity:<输入>(arg:输入)=>输入=身份;我们还可以将泛型类型写成对象字面量类型的调用签名:functionidentity(arg:Type):Type{returnarg;}letmyIdentity:{(arg:Type):Type}=身份;基于此,我们可以编写我们的第一个通用接口。我们将上面代码中的对象字面量抽取出来放到一个接口中::GenericIdentityFn=身份;有时,我们可能希望将泛型参数作为整个接口的参数移除。这使我们能够看到哪些类型正在与泛型一起使用(例如,使用Dictionary而不是Dictionary)。它还使类型参数对接口的所有其他成员可见。interfaceGenericIdentityFn{(arg:Type):Type;}functionidentity(arg:Type):Type{returnarg;}letmyIdentity:GenericIdentityFn=身份;请注意,我们的示例变为和以前不一样。我们现在有一个非泛型函数签名,而不是泛型函数,它是泛型类型的一部分。现在,当我们使用GenericIdentityFn时,我们需要指定相应的类型参数(这里:number),有效地保证它被底层函数签名使用。了解何时将类型参数直接放在调用签名中以及何时将类型参数放在接口本身中可以大大有助于描述类型在何处是通用的。除了泛型接口,我们还可以创建泛型类。请注意,我们无法创建通用枚举和命名空间。泛型类泛型类和泛型接口在结构上非常相似。泛型类会在类名后跟<>,这是一个泛型类型参数列表。类GenericNumber{zeroValue:NumType;添加:(x:NumType,y:NumType)=>NumType;}letmyGenericNumber=newGenericNumber();myGenericNumber.zeroValue=0;myGenericNumber.add=function(x,y){returnx+y;};这种使用GenericNumber类的方法非常简单,但是您可能会注意到一件事,那就是我们没有限制该类只能使用数字类型。相反,我们可以使用字符串或其他更复杂的对象。让stringNumeric=newGenericNumber();stringNumeric.zeroValue="";stringNumeric.add=function(x,y){returnx+y;};console.log(stringNumeric.add(stringNumeric.zeroValue,"test"));与接口一样,将类型参数放入类本身允许我们确保类的所有属性都使用相同的类型。正如我们在关于类的章节中提到的,一个类由两部分组成:静态部分和实例部分。泛型类的泛型只适用于实例部分而不适用于静态部分。因此,在使用泛型类时,静态成员不能使用类的类型参数。还记得泛型约束之前访问参数长度的例子吗?有时,你可能需要编写一个只对某些类型起作用的泛型函数,并且你对这些类型的特性有一定的了解。例如,在例子的loggingIdentity函数中,我们想要访问arg的length属性,但是编译器无法验证每个类型都有length属性,所以它警告我们不能假设所有类型都有这个属性。functionloggingIdentity(arg:Type):类型{console.log(arg.length);^^^^^^//类型“类型”上不存在属性“长度”。returnarg;}我们不希望函数处理任意类型,但我想限制它只处理任何具有length属性的类型。只要某个类型有这个属性,我们就允许传入这个类型,反之,要传入某个类型,至少要有这个属性。为了实现这一点,我们必须枚举对Type的要求来约束Type的类型。为此,我们创建了一个描述约束的接口。在这里,我们创建一个具有单个属性length的接口,然后使用接口和extends关键字来表达我们的约束:interfaceLengthwise{length:number;}functionloggingIdentity(arg:Type):Type{.log(arg.length);//现在我们知道它必须有一个length属性,所以不会抛出错误returnarg;}因为泛型函数现在被约束了,它不能再处理任意的,所有类型:loggingIdentity(3);^//“number”类型的参数不能分配给“Lengthwise”类型的参数。相反,我们传入的值类型应该具有所有必需的属性:loggingIdentity({length:10,value:3});在泛型约束中使用类型参数您可以将一个类型参数声明为由另一个类型参数绑定。例如,现在我们需要通过给定的属性名来访问一个对象的属性,那么我们必须确保我们不会意外地访问到一个对象上不存在的属性,所以我们将在这两种类型之间使用一个约束:functiongetProperty(obj:Type,key:Key){returnobj[key];}letx={a:1,b:2,c:3,d:4};getProperty(x,"a");getProperty(x,"m");^//类型“m”的参数不可分配给类型“a”的参数|“乙”|“c”|“d”'。在泛型中使用类类型在使用TypeScript泛型的前提下,如果使用工厂模式创建实例,那么需要通过构造函数引用类类型来推断类类型。例如:functioncreate(c:{new():Type}):Type{returnnewc();}一个更高级的示例将使用原型属性来推断和约束构造函数和类类型,如下所示实例之间的关系部分:BeeKeeper类{hasMask:boolean=true;}ZooKeeper类{nametag:string="Mikle";}类Animal{numLegs:number=4;}classBeeextendsAnimal{keeper:BeeKeeper=newBeeKeeper();}classLionextendsAnimal{keeper:ZooKeeper=newZooKeeper();}functioncreateInstance(c:new()=>A):A{returnnewc();}createInstance(Lion).keeper.nametag;复制代码创建实例(蜜蜂).keeper.hasMask;此模式可用于驱动混合设计模式。