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

TypeScript的Generics

时间:2023-03-28 12:14:44 HTML

前言TypeScript的官方文档已经更新,但我能找到的中文文档还是旧版本。因此,对一些新增和修改的章节进行了翻译和整理。本文整理自TypeScriptHandbook中的“泛型”一章。本文并没有严格按照原文翻译,部分内容也做了说明和补充。文字软件工程的一个重要部分是构建组件。组件不仅需要具有定义良好且一致的API,还需要可重用。好的组件不仅能兼容今天的数据类型,还能适用于未来可能出现的数据类型,这将在构建大型软件系统时给你最大的灵活性。在C#和Java等语言中,用于创建可重用组件的工具称为泛型。使用泛型,我们可以创建一个支持多种类型的组件,允许用户使用他们自己的类型来使用这些组件。HelloWorldofGenerics让我们开始编写我们的第一个泛型类型,一个恒等函数。所谓恒等函数就是传入什么就返回什么的函数,你也可以理解为类似echo命令。我们可能需要为身份函数提供一个具体类型,而不是泛型:functionidentity(arg:number):number{returnarg;}或者,我们使用任何类型:functionidentity(arg:any):any{returnarg;虽然使用any类型允许我们接受任何类型的arg参数,但它也允许我们在函数返回时丢失类型信息。如果我们传入一个数字,我们知道的唯一信息就是该函数可以返回任何类型的值。所以我们需要一种方法来捕获参数的类型,然后用它来表示返回值的类型。这里我们使用类型变量,一种特殊类型的变量,适用于类型而不是值。functionidentity(arg:Type):Type{returnarg;}现在我们在identity函数中添加了一个类型变量Type,这个Type允许我们捕获用户提供的类型,这样我们接下来就可以使用这个类型。在这里,我们再次使用Type作为返回值的类型。在目前的写法中,我们可以清楚的知道参数的类型和返回值是一样的。现在这个版本的identityfunction是泛型类型,可以支持传入多种类型。与使用any不同,它不会丢失任何信息,并且与第一个使用数字作为参数和返回类型的恒等式函数一样准确。在我们编写了一个通用的身份函数之后,我们有两种方式来调用它。第一种方式是传入所有参数,包括类型参数:letoutput=identity("myString");//letoutput:string在这里,我们使用<>而不是()来包装参数,并将Type显式设置为string作为函数调用的参数。第二种方法可能更常见。这里我们使用类型参数推断(有些中文文档会翻译成“类型推断”)。我们希望编译器能够根据我们传入的参数,自动推断并设置Type的值。让output=identity("myString");//letoutput:string注意这次我们没有使用<>显式传入类型。当编译器看到myString的值时,它会自动将Type设置为其类型(即字符串)。类型参数推断是一个有用的工具,可以使我们的代码更短且更易于阅读。在一些更复杂的示例中,当编译器无法推断类型时,您需要像上一个示例一样显式传入参数。使用泛型类型变量当您创建像identity这样的泛型函数时,您会发现编译器会强制您在函数体中正确使用这些类型参数。这意味着您必须认真对待这些参数,考虑到它们可能是任何类型,甚至是所有类型(例如使用联合类型)。我们以identity函数为例:functionidentity(arg:Type):Type{returnarg;}如果我们想打印arg参数的长度怎么办?我们可能会尝试这样写:functionloggingIdentity(arg:Type):Type{console.log(arg.length);//类型“Type”上不存在属性“length”。returnarg;}如果我们这样做,编译器会抱怨我们正在使用arg的.length属性,但我们并没有在其他地方声明arg有这个属性。我们前面也说过,这些类型变量代表任何或所有类型。所以完全有可能调用的时候传入了一个number类型,但是number没有.length属性。现在假设此函数采用类型数组而不是类型数组。由于我们使用的是数组,因此.length属性必须存在。我们可以像创建其他类型的数组一样编写:functionloggingIdentity(arg:Type[]):Type[]{console.log(arg.length);returnarg;}你可以这样理解loggingIdentity的类型:泛型函数loggingIdentity接受一个Type类型参数和一个实参arg,实参arg是一个Type类型的数组。相反,该函数返回一个Type类型的数组。如果我们传入一个全数值类型的数组,那么我们的返回值也是一个全数值类型的数组,因为Type会作为一个数字传入。现在我们使用类型变量Type作为我们使用的类型的一部分,而不是之前的整个类型,这会给我们更多的自由度。这个例子我们也可以这样写,效果一样:functionloggingIdentity(arg:Array):Array{console.log(arg.length);//Arrayhasa.length,sonomoreerrorreturnarg;}GenericTypes在上一章中,我们创建了一个支持传入不同类型的通用标识函数。在本章中,我们将探讨函数本身的类型,以及如何创建通用接口。泛型函数的形式与其他非泛型函数的形式相同。它需要先列出一个类型参数列表,有点像函数声明:functionidentity(arg:Type):Type{returnarg;}letmyIdentity:(arg:Type)=>Type=身份;泛型类型参数可以使用不同的名称,只要数量和用法一致即可:functionidentity(arg:Type):Type{returnarg;}letmyIdentity:(arg:I??nput)=>Input=身份;我们也可以把这个泛型写成对象类型的调用签名的形式:functionidentity(arg:Type):Type{returnarg;}letmyIdentity:{(arg:Type):Type}=身份;这导致我们编写第一个通用接口,让我们使用前面示例中的对象字面量,并将其代码移至接口:interfaceGenericIdentityFn{(arg:Type):Type;}functionidentity(arg:Type):Type{returnarg;}让myIdentity:GenericIdentityFn=identity;有时,我们会希望使用泛型参数作为整个接口的参数,这样可以让我们清楚地知道传入的参数是什么(例如:Dictionary而不是Dictionary)。而界面中的其他成员也能看到。interfaceGenericIdentityFn{(arg:Type):Type;}functionidentity(arg:Type):Type{returnarg;}letmyIdentity:GenericIdentityFn=身份;请注意,在这个例子中,我们只做了一些改动。不是描述泛型函数,而是将非泛型函数签名作为泛型类型的一部分包含在内。现在当我们使用GenericIdentityFn时,我们需要明确给出参数的类型。(在本例中为number),有效地锁定了调用签名使用的类型。在描述包含泛型的类型时,了解何时将类型参数放入调用签名以及何时将其放入接口非常有用。除了泛型接口,我们还可以创建泛型类。请注意,无法创建通用枚举类型和通用命名空间。泛型类(GenericClasses)泛型类的编写类似于泛型接口。在类名之后,用尖括号<>包裹类型参数列表:classGenericNumber{zeroValue:NumType;添加:(x:NumType,y:NumType)=>NumType;}letmyGenericNumber=newGenericNumber();myGenericNumber.zeroValue=0;myGenericNumber.add=function(x,y){returnx+y;};在此示例中,您不限于数字类型。我们还可以使用字符串或更复杂的类型:console.log(stringNumeric.add(stringNumeric.zeroValue,"test"));就像接口一样,将类型参数放在类上可确保类中的所有属性都使用相同的类型。正如我们在类一章中提到的,类的类型有两个部分:静态部分和实例部分。泛型类只作用于实例,所以我们在使用类的时候要注意静态成员不能使用类型参数。GenericConstraints在前面的loggingIdentity例子中,我们想获取参数arg的.length属性,但是编译器无法证明每个类型都有.length属性,所以会提示错误:functionloggingIdentity(arg:类型:类型{console.log(arg.length);//类型“Type”上不存在属性“length”。returnarg;}比起兼容任何类型,我们更愿意约束这个函数,让它只能使用具有.length属性的类型。只要类型有这个成员,我们就可以使用它,但它必须至少有这个成员。为此,我们需要列出类型约束的必要条件。为此,我们需要创建一个描述约束的接口。在这里,我们创建一个只有.length属性的接口,然后我们使用这个接口和扩展关键字实现约束:interfaceLengthwise{length:number;}functionloggingIdentity(arg:Type):Type{console.log(arg.length);//现在我们知道它有一个.length属性,所以不再有错误returnarg;}现在这个通用函数被限制了,它不再适用于所有类型:loggingIdentity(3);//'number'类型的参数不能分配给'Lengthwise'类型的参数。我们需要传入一个满足约束的值:loggingIdentity({length:10,value:3});在泛型约束中使用类型参数(UsingTypeParametersinGenericConstraints)你可以声明一个类型参数,它被其他类型参数约束。例如,我们想要获取对象的给定属性名称的值,为此,我们需要确保我们不会获取obj上不存在的属性。所以我们在两种类型之间创建一个约束: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;}classZooKeeper{nametag:string="Mikle";}classAnimal{numLegs:number=4;}classBeeextendsAnimal{keeper:BeeKeeper=newBeeKeeper();}classLionextends动物{keeper:ZooKeeper=newZooKeeper();}functioncreateInstance(c:new()=>A):A{returnnewc();}createInstance(Lion).keeper.nametag;createInstance(Bee).keeper.hasMask;TypeScript系列TypeScript系列文章由官方文档翻译、重点难点解析、实战技巧三部分组成,涵盖入门、进阶、实战。旨在为您提供系统的TS学习教程。预计约有40篇文章。点此浏览全系列文章,建议顺便收藏本站。微信:“mqyqingfeng”,加我到世优唯一的读者群。如有错误或不准确的地方,请务必指正,万分感谢。如果你喜欢或者有启发,欢迎star,这也是对作者的鼓励。