介绍 TypeScript是JavaScript语言的扩展,它使用JavaScript运行时和编译时类型检查器。 TypeScript提供了多种方法来表示代码中的对象,其中一种是使用接口。TypeScript中的接口有两种使用场景:您可以创建类必须遵循的约定,例如,这些类必须实现的成员,还可以在应用程序中表示类型,就像普通的类型声明一样。 您可能会注意到接口和类型共享一组相似的功能。 事实上,一个几乎总是可以替代另一个。 主要区别在于接口可能对同一个接口有多个声明,TypeScript将合并这些声明,而类型只能声明一次。您还可以使用类型来创建原始类型(例如字符串和布尔值)的别名,这是接口无法做到的。 TypeScript中的接口是表示类型结构的强大方法。它们允许您以类型安全的方式使用这些结构并同时记录它们,从而直接改善开发人员体验。 在今天的文章中,我们将在TypeScript中创建接口,学习如何使用它们,并了解普通类型和接口之间的区别。 我们将尝试不同的代码示例,可以在TypeScript环境或TypeScriptPlayground(一个允许您直接在浏览器中编写TypeScript的在线环境)中遵循这些示例。准备工作 要完成今天的示例,我们将需要做如下准备工作:一个环境。我们可以执行TypeScript程序以跟随示例。要在本地计算机上进行设置,我们将需要准备以下内容。为了运行处理TypeScript相关包的开发环境,同时安装了Node和npm(或yarn)。本文教程中使用Node。js版本为14。3。0和npm版本6。14。5进行了测试。要在macOS或Ubuntu18。04上安装,请按照如何在macOS上安装Node。js和创建本地开发环境或如何在Ubuntu18。04上安装Node。js的使用PPA安装部分中的步骤进行操作。如果您使用的是适用于Linux的Windows子系统(WSL),这也适用。此外,我们需要在机器上安装TypeScript编译器(tsc)。为此,请参阅官方TypeScript网站。如果你不想在本地机器上创建TypeScript环境,你可以使用官方的TypeScriptPlayground来跟随。您将需要足够的JavaScript知识,尤其是ES6语法,例如解构、rest运算符和导入导出。如果您需要有关这些主题的更多信息,建议阅读我们的如何用JavaScript编写代码系列。本文教程将参考支持TypeScript并显示内联错误的文本编辑器的各个方面。这不是使用TypeScript所必需的,但确实可以更多地利用TypeScript功能。为了获得这些好处,您可以使用像VisualStudioCode这样的文本编辑器,它完全支持开箱即用的TypeScript。你也可以在TypeScriptPlayground中尝试这些好处。 本教程中显示的所有示例都是使用TypeScript4。2。2版创建的。在TypeScript中创建和使用接口 在本节中,我们将使用TypeScript中可用的不同功能创建接口,您还将学习如何使用您创建的接口。 TypeScript中的接口是通过使用interface关键字后跟接口名称,然后是带有接口主体的{}块来创建的。例如,这里是一个Logger接口:interfaceLogger{log:(message:string)void;} 与使用类型声明创建普通类型类似,我们可以在{}中指定类型的字段及其类型:interfaceLogger{log:(message:string)void;} Logger接口表示一个对象,该对象具有一个名为log的属性。此属性是一个接受字符串类型的单个参数并返回void的函数。 我们可以将Logger接口用作任何其他类型。下面是一个创建与Logger接口匹配的对象字面量的示例:interfaceLogger{log:(message:string)void;}constlogger:Logger{log:(message)console。log(message),}; 使用Logger接口作为其类型的值必须具有与Logger接口声明中指定的成员相同的成员。如果某些成员是可选的,则可以省略它们。 由于值必须遵循接口中声明的内容,因此,添加无关字段将导致编译错误。例如,在对象字面量中,尝试添加接口中缺少的新属性:interfaceLogger{log:(message:string)void;}constlogger:Logger{log:(message)console。log(message),otherProp:true,}; 在这种情况下,TypeScript编译器会发出错误2322,因为Logger接口声明中不存在此属性:OutputType{log:(message:string)void;otherProp:boolean;}isnotassignabletotypeLogger。Objectliteralmayonlyspecifyknownproperties,andotherPropdoesnotexistintypeLogger。(2322) 与使用普通类型声明类似,可以通过附加?将属性转换为可选属性。以他们的名义。扩展其他类型 创建接口时,我们可以从不同的对象类型进行扩展,允许您的接口包含来自扩展类型的所有类型信息。这使您能够编写具有一组通用字段的小型接口,并将它们用作构建块来创建新接口。 想象一下,我们如果有一个Clearable接口,比如这个:interfaceClearable{clear:()void;} 然后,我们可以创建一个从它扩展的新接口,继承它的所有字段。在以下示例中,接口Logger是从Clearable接口扩展而来的。注意突出显示的行:interfaceClearable{clear:()void;}interfaceLoggerextendsClearable{log:(message:string)void;} Logger接口现在还有一个clear成员,它是一个不接受参数并返回void的函数。这个新成员继承自Clearable接口。就像我们这样做一样:interfaceLogger{log:(message:string)void;clear:()void;} 当使用一组通用字段编写大量接口时,我们可以将它们提取到不同的接口并更改接口以扩展创建的新接口。 回到前面使用的Clearable示例,假设我们的应用程序需要一个不同的接口,例如下面的StringList接口,来表示一个包含多个字符串的数据结构:interfaceStringList{push:(value:string)void;get:()string〔〕;} 通过使这个新的StringList接口扩展现有的Clearable接口,指定该接口还具有在Clearable接口中设置的成员,将clear属性添加到StringList接口的类型定义中: interfaceStringListextendsClearable{push:(value:string)void;get:()string〔〕;} 接口可以从任何对象类型扩展,例如接口、普通类型,甚至是类。带有可调用签名的接口 如果接口也是可调用的(也就是说,它也是一个函数),我们可以通过创建可调用签名在接口声明中传达该信息。 通过在未绑定到任何成员的接口内添加函数声明并在设置函数的返回类型时使用:而不是来创建可调用签名。 例如,在Logger界面中添加一个可调用的签名,如下面突出显示的代码所示:interfaceLogger{(message:string):void;log:(message:string)void;} 请注意,可调用签名类似于匿名函数的类型声明,但在返回类型中,我们使用的是:而不是。这意味着绑定到Logger接口类型的任何值都可以作为函数直接调用。 要创建与Logger接口匹配的值,我们需要考虑接口的要求:它必须是可调用的。它必须有一个名为log的属性,该属性是一个接受单个字符串参数的函数。 让我们创建一个名为logger的变量,它可以分配给Logger接口的类型: interfaceLogger{(message:string):void;log:(message:string)void;}constlogger:Logger(message:string){console。log(message);}logger。log(message:string){console。log(message);} 要匹配Logger接口,该值必须是可调用的,这就是我们将logger变量分配给函数的原因:interfaceLogger{(message:string):void;log:(message:string)void;}constlogger:Logger(message:string){console。log(message);}logger。log(message:string){console。log(message);} 然后,我们将log属性添加到logger函数:interfaceLogger{(message:string):void;log:(message:string)void;}constlogger:Logger(message:string){console。log(message);}logger。log(message:string){console。log(message);} 这是Logger接口所要求的。绑定到Logger接口的值还必须具有log属性,该属性是一个接受单个字符串参数并返回void的函数。 如果我们没有包含log属性,TypeScriptCompiler会给你错误2741:OutputPropertylogismissingintype(message:string)voidbutrequiredintypeLogger。(2741) 如果logger变量中的log属性具有不兼容的类型签名,TypeScript编译器将发出类似的错误,例如将其设置为true: interfaceLogger{(message:string):void;log:(message:string)void;}constlogger:Logger(message:string){console。log(message);}logger。logtrue; 在这种情况下,TypeScript编译器会显示错误2322: OutputTypebooleanisnotassignabletotype(message:string)void。(2322) 将变量设置为具有特定类型的一个很好的功能,在这种情况下,将记录器变量设置为具有记录器接口的类型,TypeScript现在可以推断记录器函数和日志中函数的参数类型财产。 我们可以通过从两个函数的参数中删除类型信息来检查。请注意,在下面突出显示的代码中,消息参数没有类型:interfaceLogger{(message:string):void;log:(message:string)void;}constlogger:Logger(message){console。log(message);}logger。log(message){console。log(message);} 在这两种情况下,编辑器应该仍然能够显示参数的类型是字符串,因为这是Logger接口所期望的类型。带有索引签名的接口 可以向界面添加索引签名,就像使用普通类型一样,从而允许界面具有无限数量的属性。 例如,如果想创建一个具有无限数量的字符串字段的DataRecord接口,可以使用以下突出显示的索引签名:interfaceDataRecord{〔key:string〕:string;} 然后,我们可以使用DataRecord接口设置具有多个字符串类型参数的任何对象的类型:interfaceDataRecord{〔key:string〕:string;}constdata:DataRecord{fieldA:valueA,fieldB:valueB,fieldC:valueC,。。。}; 在本文中,我们使用TypeScript中可用的不同功能创建了接口,并学习了如何使用您创建的接口。 在接下来的内容中,我们将了解更多关于类型和接口声明之间的区别,并获得声明合并和模块扩充的实践。类型和接口的区别 到目前为止,我们已经看到接口声明和类型声明是相似的,具有几乎相同的特性集。 例如,我们创建了一个从Clearable接口扩展而来的Logger接口:interfaceClearable{clear:()void;}interfaceLoggerextendsClearable{log:(message:string)void;} 可以使用两种类型声明来复制相同的类型表示:typeClearable{clear:()void;}typeLoggerClearable{log:(message:string)void;} 如前面内容所示,接口声明可用于表示各种对象,从函数到具有无限数量属性的复杂对象。这也适用于类型声明,甚至从其他类型扩展,因为,我们可以使用交集运算符将多个类型相交。 由于类型声明和接口声明非常相似,因此,需要考虑各自独有的特定功能,并在代码库中保持一致。选择一种在代码库中创建类型表示,并且仅在需要仅对它可用的特定功能时才使用另一种。 例如,类型声明具有接口声明所缺乏的一些特性,例如:联合类型。映射类型。原始类型的别名。 仅可用于接口声明的功能之一是声明合并,我们将在接下来的内容中学习它。重要的是要注意,如果您正在编写一个库并希望为库用户提供扩展库提供的类型的能力,那么声明合并可能很有用,因为类型声明无法做到这一点。声明合并 TypeScript可以将多个声明合并为一个声明,使他们能够为同一个数据结构编写多个声明,并在编译期间将它们捆绑在一起,就像它们是一个单一类型一样。 在文中,我们将看到它是如何工作的,以及为什么它在使用接口时很有帮助。 TypeScript中的接口可以重新打开;也就是说,可以合并同一接口的多个声明。当我们想要将新字段添加到现有界面时,这很有用。 例如,假设我们有一个名为Databaseoptions的接口,如下所示:interfaceDatabaseOptions{host:string;port:number;user:string;password:string;} 此接口将用于在连接到数据库时传递选项。 稍后在代码中,声明一个具有相同名称但具有一个名为dsnUrl的字符串字段的接口,如下所示interfaceDatabaseOptions{dsnUrl:string;} 当TypeScript编译器开始读取我们的代码时,它会将DatabaseOptions接口的所有声明合并为一个。从TypeScript编译器的角度来看,DatabaseOptions现在是:interfaceDatabaseOptions{host:string;port:number;user:string;password:string;dsnUrl:string;} 该接口包括我们最初声明的所有字段,以及我们单独声明的新字段dsnUrl。两个声明已合并。模块扩充 当我们需要使用新属性扩充现有模块时,声明合并很有帮助。一个用例是,当我们向库提供的数据结构添加更多字段时。这在名为express的Node。js库中相对常见,它允许我们创建HTTP服务器。 使用express时,一个Request和一个Response对象被传递给我们的请求处理程序(负责为HTTP请求提供响应的函数)。Request对象通常用于存储特定于特定请求的数据。例如,我们可以使用它来存储发出初始HTTP请求的登录用户:constmyRoute(req:Request,res:Response){res。json({user:req。user});} 在这里,请求处理程序将用户字段设置为登录用户的json发送回客户端。使用负责用户身份验证的快速中间件,将登录的用户添加到代码中另一个位置的请求对象。 Request接口本身的类型定义没有用户字段,因此上面的代码会给出类型错误2339:PropertyuserdoesnotexistontypeRequest。(2339) 要解决这个问题,我们必须为express包创建一个模块扩充,利用声明合并向请求接口添加一个新属性。 如果我们在express类型声明中检查Request对象的类型,我们会注意到它是一个添加在名为Express的全局命名空间中的接口,如DefinitiveTyped存储库中的文档所示:declareglobal{namespaceExpress{Theseopeninterfacesmaybeextendedinanapplicationspecificmannerviadeclarationmerging。Seeforexamplemethodoverride。d。ts(https:github。comDefinitelyTypedDefinitelyTypedblobmastertypesmethodoverrideindex。d。ts)interfaceRequest{}interfaceResponse{}interfaceApplication{}}} 注意:类型声明文件是只包含类型信息的文件。DefinitiveTyped存储库是为没有类型声明的包提交类型声明的官方存储库。npm上可用的types包是从此存储库发布的。 要使用模块扩充向Request接口添加新属性,我们必须在本地类型声明文件中复制相同的结构。例如,假设我们创建了一个名为express。d。ts的文件,如下所示,然后将其添加到tsconfig。json的types选项中:importexpress;declareglobal{namespaceExpress{interfaceRequest{user:{name:string;}}}} 从TypeScript编译器的角度来看,Request接口有一个用户属性,它们的类型设置为一个对象,该对象具有一个称为字符串类型名称的属性。发生这种情况是因为同一接口的所有声明都被合并了。 假设我们正在创建一个库,并希望为我们的库的用户提供增加自己的库提供的类型的选项,就像我们在上面使用express所做的那样。在这种情况下,我们需要从库中导出接口,因为普通类型声明不支持模块扩充。结论 到这里,在本文提供的教程就结束了。 我们编写了多个TypeScript接口来表示各种数据结构,发现了如何将不同的接口一起用作构建块来创建强大的类型,并了解了普通类型声明和接口之间的区别。 我们现在可以开始为代码库中的数据结构编写接口,让我们拥有类型安全的代码和文档。