目录介绍语法内容模块模块语法三斜线指令参考amd-module场景内部项目中为内部项目编写声明文件为第三方包编写声明文件全局变量的第三方库修改声明修改全局变量的模块的第三方库。修改windowESM和CommonJSUMD模块插件汇总。简介声明文件是一个后缀为.检查。(注意同目录下最好不要有同名的.ts文件和.d.ts文件,比如lib.ts和lib.d.ts,否则模块系统无法根据文件名加载模块only)为什么需要申报文件?我们知道TypeScript是根据类型声明来进行类型检查的,但是有些情况下可能没有类型声明:第三方包,因为第三方包是用JavaScript语法封装的,不是TypeScript,没有类型。宿主环境扩展,比如一些混合环境,在window变量下有一些桥接接口,这些接口没有类型声明。如果没有类型声明,在使用变量、调用函数或实例化类时,TypeScript的类型检查将无法通过。声明文件就是针对这些情况的,开发者在声明文件中写入第三方模块的类型声明/宿主环境的类型声明。让TypeScript正常进行类型检查。此外,还可以导入声明文件以使用其中公开的类型定义。简而言之,声明文件有两个用途:通过import导入,使用其中暴露的类型定义和变量声明。关联相关模块,为模块做类型声明。第二种用法,声明文件如何与相关模块关联?例如,如果有一个名为“foo”的第三方包,那么TypeScript会根据其package.json的types和typing字段在node_modules/foo中寻找声明文件,找到的声明文件将作为模块的声明文件;在node_modules/@types/foo/目录中找到声明文件。如果能找到,就作为foo模块的声明文件;TypeScript还将在我们的项目中搜索.d.ts文件。如果遇到declaremodule'foo'语句,则该声明将用作foo模块的声明。综上所述,TypeScript会读取特定目录下的指定声明文件。在内部项目中,TypeScript读取tsconfig.json中的文件集,并处理其中的声明文件。读取node_modules中各个第三方包的package.json的类型或类型指定的文件。读取@types目录下同名包的声明文件。声明文件中的代码不会出现在最终的编译结果中。编译后,转换后的JavaScript代码会输出到“outDir”选项指定的目录中,.ts模块中使用的值的声明会输出到“declarationDir”指定的目录中。.ts文件中的声明语句在编译后会被去掉,比如declareleta:number;出口默认一个;将被编译为"usestrict";exports.__esModule=true;exports["default"]=a;TypeScript编译过程不仅将TypeScript语法翻译成ES6/ES5,还会输出其中使用的值的类型.ts文件中的代码到指定的声明文件。如果你需要实现一个库项目,这个特性很有用,因为使用你的库的项目可以直接使用这些声明文件,而不需要你为你的库编写声明文件。TypeScript中的语法内容声明创建三种类型的实体之一:名称空间、类型或值。命名空间最终被编译成一个全局变量,所以我们也可以认为在声明文件中实际上创建了两个实体,类型和值。也就是说,定义一个类型或声明一个值。//Type接口interfacePerson{name:string;}//Type类型别名typeFruit={size:number};//值变量声明leta:number;//值函数declarefunctionlog(message:string):void;//ValueclassdeclareclassPerson{name:string;}//ValueenumerationdeclareenumColor{Red,Green}//Valuenamespacedeclarenamespaceperson{letname:string;}我们注意到类型可以直接定义,但是值的声明需要使用declare关键字,因为如果不使用declare关键字,值的声明和初始化是在一起的,比如leta:number;//编译为vara;但是编译结果会去掉所有的声明语句,保留初始化部分,而声明文件中的内容只是为了声明,所以需要通过declare来标识。这只是一个声明语句,在编译时可以直接去掉。TypeScript也限制语句文件声明一个值必须使用declare,否则会认为有初始化内容,会报错。//foo.d.tsleta:number=1;//errorTS1039:Initializersarenotallowedinambientcontexts.declare也允许出现在.ts文件中,但一般不这样做,直接在.ts文件中使用let/const/function/class就可以声明并初始化一个多变的。并且.ts文件编译后declare语句也会被去掉,所以不需要declare语句。注意声明多个同名变量会发生冲突declareletfoo:number;//错误TS2451:无法重新声明块作用域变量“a”。声明letfoo:number;//错误TS2451:无法重新声明块作用域变量“a”。declare除了可以用来声明一个值,declare还可以用来声明一个模块和一个全局插件。这两个用法用于在特定场景下声明第三方包。declaremodule用于声明第三方模块的类型,比如有一个第三方包foo,它没有类型声明。我们可以在我们的项目中实现一个声明文件,让TypeScript识别模块类型:foo.d.ts//foo.d.tsdeclaremodule'foo'{exportletsize:number;}然后我们可以使用:importfoofrom'富';控制台日志(foo.size);eclare模块不仅可以用来声明模块的类型,还可以用来实现模块插件的声明。将在下一小节中介绍。declareglobal用于声明对global进行扩展的第三方包,后面会介绍。模块化模块语法声明文件的模块化语法类似于.ts模块的语法,但有一些细微差别。.ts导出模块(typescript会根据导出的模块判断类型),.d.ts导出类型的定义和声明值。声明文件可以导出类型和值声明//index.d.ts//导出值声明exportleta:number;//导出类型导出接口Person{name:string;};声明文件可以引入其他声明文件,甚至可以导入其他.ts文件(因为.ts文件也可能导出类型)//Person.d.tsexportdefaultinterfacePerson{name:string}//index.d.tsimportPersonfrom'。/人';exportletp:人;如果不导出声明文件,则默认全局访问//person.d.tsinterfacePerson{name:string}declareletp:Person;//index.tsletp1:Person={name:'Sam'};控制台日志(p);如果你使用模块导出语法(ESM/CommJS/UMD),它不会被解析为全局(当然UMD仍然可以全局访问)。//ESM接口Person{name:string}exportletp:Person;exportdefaultPerson;//CommonJSinterfacePerson{name:string}declareletp:Person;export=p;//UMDinterfacePerson{name:string}declareletp:Person;export=p;exportasnamespacep;注意:UMDpackageexportasnamespace语法只能出现在声明文件中。三斜杠指令声明文件中的三斜杠指令用于控制编译过程。三斜杠指令只能放在包含它的文件的最顶部。如果指定了--noResove编译选项,预编译过程将忽略三斜杠指令。reference引用指令用于表示声明文件的依赖关系。///用于告诉编译器依赖其他声明文件。路径指定的声明文件将在预处理过程中添加到编译器中。路径是相对于文件本身的。引用不存在的文件或引用自身将导致错误。///用于告诉编译器它依赖于node_modules/@types/node/index.d.ts。如果你的项目依赖@types中的某些声明文件,编译后输出的声明文件中会自动添加这条指令,表示你的项目中的声明文件依赖@types中的相关声明文件。///,这里涉及到两个编译选项,--noLib,设置这个编译选项后,编译器会忽略默认库,默认库是在安装TypeScript时自动安装的imported,此文件包含存在于JavaScript运行时(例如窗口)以及DOM中的各种常见环境声明。但如果您的项目运行环境与标准的基于浏览器的运行环境有很大不同,您可能需要排除默认库。排除默认的lib.d.ts文件后,您可以在编译上下文中包含一个类似于的名称,TypeScript将提取该名称以进行类型检查。另一个编译选项是--skipDefaultLibCheck这个选项会让编译器忽略包含///指令的声明文件。您会注意到默认库顶部的这个三斜杠指令,因此如果使用--skipDefaultLibCheck编译选项,默认库也将被忽略。amd-moduleamd-module相关指令用于控制打包成amd模块的编译过程///该指令用于告诉编译器将模块名传入MODULE打包为AMD(defaultcaseisanonymous)///exportclassC{}compilestodefine("NamedModule",["require","exports"],function(require,exports){varC=(function(){functionC(){}returnC;})();exports.C=C;});Scenario这里我们称自己的项目代码为“内部项目”,引入的第三方模块,包括npm-introduced和script-introduced,称为“外部模块”。在内部项目中为内部项目写一个声明文件在自己的项目中,为自己的模块写一个声明文件,比如多个模块共享的类型,就可以写一个声明文件。这种场景通常是不必要的,通常是某个.ts文件导出声明,其他模块引用声明。为第三方包编写声明文件为第三方包编写声明文件分为在内部项目中为第三方包编写声明文件和在外部模块中为外部模块编写声明文件。为内部项目编写第三方包的声明文件:如果第三方包没有TS声明文件,为了保证第三方包能通过类型检查,使用第三方包-partypackagesafety,需要在内部工程中编写第一个声明文件三方包的声明文件。在外部模块中写一个外部模块的声明文件:如果你是第三方库的作者,无论你是否使用TypeScript开发该库,你都应该提供一个声明文件,这样使用TypeScript开发的项目才能更好的进行使用你的库,那么你需要编写你的声明文件。这两种情况下声明文件的语法类似,只是个别声明语法和文件处理不同:内部项目为第三方包写声明文件时,可以以.d.ts命名,然后在tsconfig.json文件中配置,并在include文件中包含即可。外部模块的声明文件需要打包到输出目录,package.json中的type字段指定声明文件的位置;或将其上传到@types/,使用或通过npminstall@types/安装声明文件。Redux在tsconfig.json中指定declarationDir为./types,TypeScript会将项目的声明打包到该目录下。目录结构和源码一样,然后在redux源码入口处导出所有模块,所以types目录下也有types。一个入口声明文件index.d.ts,包含所有的导出模块声明,redux指定package.json中的types字段(或typings字段)作为入口声明文件:./types/index.d.ts。这样就自动生成了接口的声明文件。内部项目写声明文件给第三方时,如果是通过npm模块引入的,比如importmoduleNamefrom'path';然后需要通过declaremodule''语法声明模块。外部模块的声明文件都是普通的类型导出语法(如exportdefaultexport=等),如果声明文件在@types中,将使用与模块同名的声明文件作为类型声明模块的;如果声明文件在第三方包中,则TypeScript模块将其作为该第三方包模块的模块声明。当用户导入并使用该模块时,TypeScript会根据相应的声明文件进行类型提示和类型检查。根据第三方包类型,可以分为带有几个全局变量的第三方库。我们知道,如果不使用模块导出语法,声明文件中的默认声明都是全局的。declarenamespaceperson{letname:string}或interfacePerson{name:string;}declareletperson:Person;使用:console.log(person.name);修改全局变量模块的第三方库的声明如果有第三方包修改了一个全局模块(这个第三方包是这个全局模块的插件),声明文件这个第三方包根据全局模块的声明有不同的声明方式。如果全局模块使用命名空间声明declarenamespaceperson{letname:string}根据命名空间的声明合并原则,插件模块可以这样声明declarenamespaceperson{//extendstheageattributeletage:number;}如果全局模块使用全局变量声明interfacePerson{name:string;}declareletperson:Person;根据接口声明合并的原则,插件模块可以声明interfacePerson{//extendstheageattributeage:number;}上面全局模块的插件模块的声明方法可以应用到下面场景:内部项目使用插件,但是插件没有声明文件,我们可以在内部项目中自己实现声明文件。为插件模块编写声明文件并发布到@types。如果你是插件模块的作者,希望在项目中引用全局模块,并将扩展类型输出到声明文件中,供其他项目使用。可以这样实现//plugin/index.ts//注意这个声明会让TypeScript输出类型声明文件declareglobal{//假设global模块使用全局变量来声明interfacePerson{age:number}}console.log(person.age);出口{};注意declareglobal也可以写在声明文件中,但是最后必须加上export{}或者其他模块导出语句,否则会报错。另外,如果在声明文件中写declareglobal,编译后不会输出到声明文件中。修改windowwindow的类型为interfaceWindow{...},在默认库中声明。如果想扩展window变量(比如一些混合环境),可以这样实现//window.d.ts//declarationmergeinterfaceWindow{bridge:{log():void}}//或者declareglobal{interfaceWindow{bridge:{log():void}}}或者//index.tsdeclareglobal{interfaceWindow{bridge:{log():void}}}window.bridge={log(){}}出口{};ESM和CommonJS为第三方ESM或CommonJS模块编写声明文件,可以使用ESMexport或CommonJS模块语法导出,与第三方包的模块形式无关。请参见以下示例interfacePerson{name:string;}declareletperson:Person;export=person;//你也可以使用exportdefaultperson;importpersonfrom'person';控制台日志(人名);上面的声明文件是放在node_modules/@types/person/index.d.ts,或者放在node_modules/person/package.json的types或者typings字段指定的位置。如果在自己的项目中声明,应该使用declare模块实现declaremodule'person'{exportletname:string;}UMDUMD模块,并添加exportasnamespaceModuleName;基于CommonJS声明的声明。请参阅以下ESM示例//node_modules/@types/person/index.d.tsinterfacePerson{name:string;}declareletperson:Person;导出默认人;导出为命名空间人;可以通过导入//src/index.tsimportpersonfrom'person'来访问;控制台日志(人名);也可以全局访问//src/index.ts//注意如果使用ESM导出,全局使用时先访问defalut属性。console.log(person.default.name);以下是CommonJS的示例//node_modules/@types/person/index.d.tsinterfacePerson{name:string;}declareletperson:Person;导出默认人;导出为命名空间人;可以通过import//src/index.tsimportpersonfrom'person'访问;控制台日志(人名);也可以全局访问//src/index.tsconsole.log(person.name);Module插件上面我们提到了declaremodule不仅可以用来为第三方模块声明一个类型,还可以可用于为第三方模块的插件模块声明类型。//types/moment-plugin/index.d.ts//如果moment定义为UMD,则无需导入,直接import*asmomentfrom'moment'即可;declaremodule'moment'{exportfunctionfoo():moment.CalendarKey;}//src/index.tsimport*asmomentfrom'moment';import'moment-plugin';时刻.foo();比如redux-thunk作为redux插件extend-redux的声明文件。d.ts,声明如下//node_modules/redux-thunk/extend-redux.d.tsdeclaremodule'redux'{//声明代码...}