介绍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+语法,例如解构、剩余运算符和导入/导出。如果您需要有关这些主题的更多信息,我们建议您阅读我们的如何使用JavaScript编写代码系列。本教程将涉及支持TypeScript并显示内联错误的文本编辑器的各个方面。这不是使用TypeScript所必需的,但它确实更多地利用了TypeScript的特性。要获得这些好处,您可以使用像VisualStudioCode这样的文本编辑器,它完全支持开箱即用的TypeScript。您还可以在TypeScriptPlayground中尝试这些好处。本教程中显示的所有示例都是使用TypeScript版本4.2.2创建的。在TypeScript中创建和使用接口在本节中,我们将使用TypeScript中提供的不同功能创建接口,您还将学习如何使用您创建的接口。TypeScript中的接口是通过使用interface关键字后跟接口名称,然后是带有接口主体的{}块来创建的。例如这里有一个Logger接口:interfaceLogger{log:(message:string)=>void;}类似于使用类型声明创建普通类型,我们可以在{}中指定类型的字段及其类型:interfaceLogger{log:(message:string)=>void;}Logger接口表示具有名为log的属性的对象。此属性是一个函数,它接受一个字符串类型的参数并返回void。我们可以像使用任何其他类型一样使用Logger接口。下面是一个创建与Logger接口相匹配的对象字面量的示例:;使用Logger接口作为其类型的值必须具有与Logger接口声明中指定的相同的成员。如果某些成员是可选的,则可以省略它们。由于该值必须符合接口中声明的内容,因此添加无关字段将导致编译错误。例如,在对象文字中,尝试添加接口中缺少的新属性:interfaceLogger{log:(message:string)=>void;}constlogger:Logger={log:(message)=>console.日志(消息),otherProp:true,};在这种情况下,TypeScript编译器会发出错误2322,因为此属性在Logger接口声明中不存在:OutputType'{log:(message:string)=>void;其他道具:布尔值;}'不可分配给类型'Logger'。对象字面量只能指定已知属性,并且类型“Logger”中不存在“otherProp”。(2322)与使用普通类型声明类似,属性可以转换为可选属性。以他们的名义。当通过扩展其他类型创建接口时,我们可以从不同的对象类型进行扩展,让你的接口包含扩展类型的所有类型信息。这使您能够编写具有一组通用字段的小型接口,并将它们用作构建块来创建新接口。想象一下,如果我们有一个Clearable接口,像这样:interfaceClearable{clear:()=>void;}然后,我们可以创建一个从它扩展的新接口,继承它的所有字段。在以下示例中,接口Logger是从Clearable接口扩展而来的。注意突出显示的行:xxxxxxxxxx1interfaceClearable{2clear:()=>void;3}4interfaceLoggerextendsClearable{5log:(message:string)=>void;6}7Logger接口现在也有一个clear成员,它是一个不带参数并返回void的函数。这个新成员继承自Clearable接口。就像我们做的:xxxxxxxxxxx1interfaceLogger{2log:(message:string)=>void;3clear:()=>void;4}5当编写大量具有一组通用字段的接口时,我们可以把它们提取到不同的接口并更改接口以扩展创建的新接口。回到我们之前使用的Clearable例子,假设我们的应用程序需要一个不同的接口,比如下面的StringList接口,来表示一个包含多个字符串的数据结构:interfaceStringList{push:(value:string)=>void;get:()=>string[];}通过使这个新的StringList接口扩展现有的Clearable接口,将clear属性添加到StringList接口的类型定义中,指定该接口还具有在Clearable接口中设置的成员:interfaceStringListextendsClearable{push:(value:string)=>void;get:()=>string[];}接口可以从任何对象类型扩展,例如接口、普通类型,甚至类。带有可调用签名的接口如果一个接口也是可调用的(也就是说,它也是一个函数),我们可以通过创建一个可调用签名在接口声明中传达该信息。通过在未绑定到任何成员的接口内添加函数声明并在设置函数的返回类型时使用:而不是=>来创建可调用签名。例如,为Logger接口添加一个可调用的签名,如下面高亮代码所示:interfaceLogger{(message:string):void;log:(message:string)=>void;}注意callable的签名类似于匿名函数的类型声明,但是在返回类型中我们使用:而不是=>。这意味着绑定到Logger接口类型的任何值都可以作为函数直接调用。要创建一个匹配Logger接口的值,我们需要考虑接口的要求:它必须是可调用的。它必须有一个名为log的属性,它是一个接受单个字符串参数的函数。让我们创建一个名为logger的变量,它可以分配给Logger接口的类型:interfaceLogger{(message:string):void;日志:(消息:字符串)=>void;}常量记录器:记录器=(消息:字符串)=>{console.log(消息);}logger.log=(消息:字符串)=>{console.log(消息);}要匹配Logger接口,该值必须是可调用的,这就是我们将logger变量赋值给函数的原因:interfaceLogger{(message:string):void;日志:(消息:字符串)=>void;}常量记录器:记录器=(消息:字符串)=>{console.log(消息);}logger.log=(消息:字符串)=>{console.log(消息);}然后,我们将日志属性添加到记录器函数:interfaceLogger{(message:string):void;日志:(消息:字符串)=>void;}const记录器:Logger=(消息:字符串)=>{console.log(消息);}logger.log=(消息:字符串)=>{console.log(消息);}这是接口需要的Logger。绑定到Logger接口的值还必须有一个log属性,它是一个接受单个字符串参数并返回void的函数。如果我们不包含log属性,TypeScript编译器会给你错误2741:OutputProperty'log'ismissingintype'(message:string)=>void'butrequiredintype'Logger'。(2741)如果记录器变量A属性中的日志具有不兼容的类型签名,TypeScript编译器将发出类似的错误,例如将其设置为true:interfaceLogger{(消息:字符串):void;日志:(消息:字符串)=>void;}constlogger:Logger=(消息:字符串)=>{console.log(消息);}logger.log=true;在这种情况下,TypeScript编译器会显示错误2322:OutputType'boolean'isnotassignabletotype'(message:string)=>void'。(2322)将变量设置为具有特定类型的一个很好的功能,在这种情况下,将记录器变量设置为具有记录器接口的类型,TypeScript现在可以推断函数和日志中函数的记录器参数类型属性。我们可以通过从两个函数的参数中删除类型信息来进行检查。请注意,在下面突出显示的代码中,消息参数没有类型:interfaceLogger{(message:string):void;日志:(消息:字符串)=>void;}constlogger:Logger=(消息)=>{console.log(消息);}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;}typeLogger=Clearable&{log:(message:string)=>void;}如前所示,接口声明可用于表示All各种对象,从函数到具有无限数量属性的复杂对象。这也适用于类型声明,甚至从其他类型扩展,因为我们可以使用交集运算符&来交集多个类型。因为类型声明和接口声明非常相似,所以需要考虑每个特定的特性,并在整个代码库中保持一致。选择一个在您的代码库中创建类型表示,仅当您需要仅对它可用的特定功能时才使用另一个。例如,类型声明具有一些接口声明所缺乏的特性,例如:联合类型。映射类型。原始类型的别名。仅适用于接口声明的功能之一是声明合并,我们将在接下来学习。请务必注意,如果您正在编写库并希望为库用户提供扩展库提供的类型的能力,那么声明合并可能很有用,因为类型声明无法做到这一点。DeclarationMergingTypeScript可以将多个声明合并为一个,使它们能够为同一数据结构编写多个声明,并在编译期间将它们捆绑在一起,就好像它们是一个单一类型一样。在本文中,我们将了解它是如何工作的,以及为什么它在使用界面时很有用。TypeScript中的接口可以重新打开;也就是说,可以合并同一接口的多个声明。当我们想向现有界面添加新字段时,这很有用。例如,假设我们有一个名为DatabaseOptions的接口,如下所示:interfaceDatabaseOptions{host:string;端口:编号;用户:字符串;password:string;}该接口将用于连接数据库时传递选项。在后面的代码中,声明一个同名的接口,但带有一个名为dsnUrl的字符串字段,如下所示合二为一。从TypeScript编译器的角度来看,DatabaseOptions现在是:interfaceDatabaseOptions{host:string;端口:编号;用户:字符串;密码:字符串;dsnUrl:string;}这个接口包含了我们最初声明的所有字段,加上我们单独声明的新字段dsnUrl。合并了两个声明。当我们需要用新属性扩充现有模块时,模块扩展声明合并很有用。一个用例是当我们向库提供的数据结构添加更多字段时。这在名为express的Node.js库中比较常见,它允许我们创建HTTP服务器。使用express时,一个Request和一个Response对象被传递给我们的请求处理程序(负责提供对HTTP请求的响应的函数)。请求对象通常用于存储特定于特定请求的数据。例如,我们可以用它来存储发出初始HTTP请求的登录用户:constmyRoute=(req:Request,res:Response)=>{res.json({user:req.user});}这里,请求被处理程序将用户字段设置为登录用户的json发送回客户端。使用负责用户身份验证的快速中间件,将登录用户添加到代码中另一个位置的请求对象。Request接口的类型定义本身没有user字段,所以上面的代码会给出TypeError2339:Property'user'doesnotexistontype'Request'。(2339)要解决此问题,我们必须为express包创建一个模块一个利用声明合并向请求接口添加新属性的扩展。如果我们在express类型声明中检查Request对象的类型,我们会注意到它是在名为Express的全局命名空间中添加的接口,如DefinitiveTyped存储库中所述:declareglobal{namespaceExpress{//这些开放接口可能通过声明合并以特定于应用程序的方式进行扩展。//参见示例method-override.d.ts(https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/method-override/index.d.ts)interfaceRequest{}interfaceResponse{}interfaceApplication{}}}注意:类型声明文件是只包含类型信息的文件。DefinitiveTyped存储库是为没有类型声明的包提交类型声明的官方存储库。@types/
