目录场景摘要
介绍
声明文件是后缀为.d.ts的文件。 开发者在声明文件中编译类型声明,TypeScript根据声明文件的内容进行类型检测。 (注意同一个目录下最好不要有同名的.ts文件和.d.ts,比如lib.ts和lib.d.ts,否则模块系统无法只根据文件加载模块姓名)
为什么需要声明? 我们知道TypeScript是根据类型声明来进行类型检测的,但在某些情况下可能没有类型声明:
如果没有类型声明,在使用变量、调用函数、实例化类时,TypeS编程脚本的类型检测就无法通过。
声明文件就是针对这种情况的。 开发者在声明文件中编译第三方模块的类型声明/宿主环境的类型声明。 让 TypeScript 正常进行类型检测。
此外,还可以使用其中公开的类型定义来导出声明文件。
总之,声明文件可以通过两种方式使用:
对于第二种用法,声明文件如何与相关模块关联?
例如,如果有一个名为“foo”的第三方包,那么TypeScript会根据其package.json的类型和类型数组在node_modules/foo中搜索声明文件,并将找到的声明文件用作模块的声明文件; TypeScript 还会在 node_modules/@types/foo/ 目录中查找声明文件。 如果能找到typescript合并文件,则将其作为foo模块的声明文件; TypeScript 将在我们的项目中搜索 .d.ts 文件。 如果遇到declare module 'foo'语句,则该声明被用作foo模块的声明。
综上所述,TypeScript 会读取特定目录下的指定声明文件。
声明文件中的代码不会出现在最终的编译结果中。 编译后,转换后的代码将输出到“outDir”选项指定的目录,.ts模块中使用的值的声明将输出到指定目录中的“declarationDir”。
.ts文件中的声明语句在编译后会被去掉,如
declare let a: number; export default a;
将被编译为
"use strict"; exports.__esModule = true; exports["default"] = a;
TypeScript编译过程除了将TypeScript句子翻译成ES6/ES5之外,还将代码中.ts文件中使用的值的类型输出到指定的声明文件中。 如果您需要实现一个库项目,此功能非常有用,因为使用您的库的项目可以直接使用此声明文件,而不需要您为您的库编写声明文件。
语法内容
TypeScript 中的声明创建三种类型的实体之一:命名空间、类型或值。
命名空间最终被编译成全局变量,因此我们也可以认为,虽然类型和值实体是在声明文件中创建的。 定义类型或声明值。
// 类型 接口 interface Person {name: string;} // 类型 类型别名 type Fruit = {size: number}; // 值 变量 declare let a: number; // 值 函数 declare function log(message: string): void; // 值 类 declare class Person {name: string;} // 值 枚举 declare enum Color {Red, Green} // 值 命名空间 declare namespace person {let name: string;}
我们注意到类型可以直接定义,但是值的声明需要使用declare关键字。 这是因为如果不使用declare关键字,则值的声明和初始化是在一起的,比如
let a: number; // 编译为 var a;
但编译结果会去掉所有的声明语句,保留初始化部分,而声明文件中的内容只是声明用的,所以需要用declare来标记。 这只是一个声明语句,编译时可以直接删除。
TypeScript还限制声明文件中某个值的声明必须使用declare,否则会认为有初始化的内容typescript合并文件,并报错。
// foo.d.ts let a: number = 1; // error TS1039: Initializers are not allowed in ambient contexts.
declare 也允许出现在 .ts 文件中,但通常不允许。 您可以在 .ts 文件中直接使用 let/const/function/class 来声明和初始化变量。 而且.ts文件编译完成后,declare语句也会被去掉,所以不需要declare语句。
注意声明多个同名变量会发生冲突
declare let foo: number; // error TS2451: Cannot redeclare block-scoped variaQhBcqlIxMble 'a'. declare let foo: number; // error TS2451: Cannot redeclare block-scoped variable 'a'.
除了使用declare声明值之外,declare还可以用于声明模块和全局插件。 这两种用法用于在特定场景下声明第三方包。
声明模块用于声明第三方模块的类型。 例如,有一个第三方包 foo 没有类型声明。 我们可以在项目中实现一个声明式编程工具,让 TypeScript 识别模块类型:foo.d.ts
// foo.d.ts declare module 'foo' { export let size: number; }
然后我们可以使用:
import foo from 'foo'; console.log(foo.size);
declare module不仅可以用来声明模块的类型,还可以用来声明模块插件。 将在下一小节中介绍。
declare global 用于声明扩展global的第三方包,后面会介绍。
模块模块句型
声明文件的模块化语法与.ts模块类似,但在一些细节上存在一些差异。 .ts 导入模块(typescript 将根据导入的模块确定类型),.d.ts 导入类型定义和声明的值。
声明文件可以导入类型和值的声明
// index.d.ts // 导出值声明 export let a: number; // 导出类型 export interface Person { name: string; };
声明文件可以引入其他声明文件,甚至其他.ts文件(因为.ts文件也可能导入类型)
// Person.d.ts export default interface Person {name: string} // index.d.ts import Person from './person'; export let p: Person;
如果不导入声明文件,则默认全局可访问
// person.d.ts interface Person {name: string} declare let p: Person; // index.ts let p1: Person = {name: 'Sam'}; console.log(p);
如果使用模块导入语法(ESM/CommJS/UMD),则不会被解析为全局(当然UMD仍然可以全局访问)。
// ESM interface Person {name: string} export let p: Person; export default Person;
// CommonJS interface Person {name: string} declare let p: Person; export = p;
// UMD interface Person {name: string} declare let p: Person; export = p; export as namespace p;
注意:UMD 包导出为命名空间句型只能出现在声明文件中。
三斜杠指令
声明文件中的三斜杠指令用于控制编译过程。
三斜杠指令只能放置在包含它的文件的最顶部。
如果指定了 --noResove 编译选项,则预编译过程将忽略三斜杠指令。
参考
引用命令用于指示声明文件的依赖关系。
/// 用于告诉编译器依赖的其他声明文件。 path指定的声明文件将在预处理期间添加到编译器中。 路径是相对于文件本身的。 引用不存在的文件或引用自身将导致错误。
/// 用于告诉编译器它依赖于node_modules/@types/node/index.d.ts。 如果您的项目依赖于@types中的各个声明文件,则编译后将在声明文件输出中手动添加此指令,以指示您项目中的声明文件依赖于@types中的相关声明文件。
/// ,
这涉及到两个编译选项,--noLib。 设置此编译选项后,编译器将忽略默认库。 默认库是在安装 TypeScript 时手动引入的。 该文件包含 JavaScript 运行时(例如窗口)和 DOM。 有各种常见的环境声明。 但如果您的项目的运行时环境与标准的基于浏览器的运行时环境有很大不同,您可能需要排除默认库。 排除默认的 lib.d.ts 文件后,您可以包含一个名为 的文件,TypeScript 将提取该文件以进行类型检测。
另一个编译选项是 --skipDefaultLibCheck 该选项将使编译器忽略包含 /// 指令的声明文件。 您会注意到默认库顶部也有这个三斜杠指令,因此如果使用 --skipDefaultLibCheck 编译选项,默认库也将被忽略。
amd 模块
amd-module相关指令用于控制打包成amd模块的编译过程
///该指令用于告诉编译器将模块名称传入到打包为AMD的模块中(默认是匿名的)
/// export class C { }
编译出来的结果是
define("NamedModule", ["require", "exports"], function (require, exports) { var C = (function () { function C() { } return C; })(); exports.C = C; });
场景
这里我们将自己的项目代码称为“内部项目”,导入的第三方模块,包括npm和script引入的模块,称为“外部模块”。
1、内部项目中编写内部项目申报文件
在自己的项目中,为自己的模块编写声明文件,比如多个模块共享的类型,可以编写声明文件。 这种场景一般是不必要的,通常是.ts文件导入声明,以及其他模块引用声明。
2、编写第三方包的声明文件
为第三方包编写声明文件分为在内部项目中为第三方包编写声明文件和在外部模块中为外部模块编写声明文件。
在内部项目中为第三方包编写声明文件:如果第三方包没有TS声明文件,为了保证第三方包能够通过类型测试,要使用第三方包- 第三方包安全,需要在项目内部编写第三方包的声明文件。
在外部模块中编写外部模块的声明文件:如果您是第三方库的作者,无论您是否使用 TypeScript 开发该库,您都应该提供声明文件,以便使用 TypeScript 开发的项目可以更好地使用您的库,那么你需要写你的声明文件。
这两种情况的申报文件句型相似,仅对某些申报句型和文件的处理有所不同:
根据第三方包的类型可以分为几种
全局变量的第三方库
我们知道,如果不使用模块导入句型,声明文件的默认声明是全局的。
declare namespace person { let name: string }
或者
interface Person { name: string; } declare let person: Person;
使用:
console.log(person.name);
修改全局变量的模块的第三方库声明
如果第三方包改变了一个全局模块(这个第三方包是这个全局模块的插件),这个第三方包的声明文件根据全局模块的声明有不同的声明形式
如果全局模块使用命名空间声明
declare namespace person { let name: string }
根据命名空间的声明合并原则,插件模块可以声明如下
declare namespace person { // 扩展了age属性 let age: number; }
如果全局模块使用全局变量声明
interface Person { name: string; } declare let person: Person;
根据socket的声明合并原则,插件模块可以声明如下
interface Person { // 扩展了age属性 age: number; }
上述全局模块的插件模块的声明方法可以应用于以下场景:
如果你是插件模块的作者,想要在项目中引用全局模块,并将扩展类型导出到声明文件中供其他项目使用。可以这样实现
// plugin/index.ts // 注意这样声明才会让TypeScript将类型输出声编程客栈明文件 declare global { // 假设全局模块使用全局变量的方式声明 interface Person { age: number } } console.log(person.age); export {};
注意,声明文件中也可以写declare global,但最后必须添加export {}或其他模块导入语句,否则会报错。 另外,如果在声明文件中写入declare global,则编译后不会输出到声明文件中。
修改窗口
窗口的类型是interface Window {...},它在默认库中声明。 如果你想扩展window变量(比如一些混合环境),可以这样实现
// window.d.ts // 声明合并 interface Window { bridge: {log(): void} } // 或者 declare global { interface Window { bridge: {log(): void} } }
或者
// index.ts declare global { interface Window { bridge: {log(): void} } } window.bridge = {log() {}} export {};
ESM 和 CommonJS
要为第三方ESM或CommonJS模块编写声明文件,可以使用ESM导入或CommonJS模块语句导入,无论第三方包是什么类型的模块。
参见下面的示例
interface Person { name: string; } declare let person: Person; export = person; // 也可以使用export default person;
import person from 'person'; console.log(person.name);
上述声明文件放置在node_modules/@types/person/index.d.ts中,或者放置在node_modules/person/package.json的types或typings数组指定的位置。
如果在自己的项目中声明,则应该使用declare模块来实现
declare module 'person' { export let name: string; }
UMD
对于 UMD 模块,添加导出为命名空间 ModuleName; 基于 CommonJS 声明的声明。
请参阅下面的 ESM 示例
// node_modules/@types/person/index.d.ts interface Person { name: string; } declare let person: Person; export default person; export as namespace person;
可以通过导入来访问
// src/index.ts import person from 'person'; console.log(person.name);
也可以通过以下方式在全球范围内访问
// src/index.ts // 注意如果用ESM导出,全局使用时候先访问defalut属性。 console.log(person.default.name);
下面是CommonJS的一个例子
// node_modules/@types/person/index.d.ts interface Person { name: string; } declare let person: Person; export default person; export as namespace person;
可以通过import引入访问
// src/index.ts import person from 'person'; console.log(person.name);
也可以全球访问
// src/index.ts console.log(person.name);
模块插件
上面我们提到,declare module不仅可以为第三方模块声明类型,还可以为第三方模块的插件模块声明类型。
// types/moment-plugin/index.d.ts // 如果moment定义为UMD,就不需要引入,直接能够使用 import * as moment from 'moment'; declare module 'moment' { export function foo(): moment.CalendarKey; } // src/index.ts import * as moment from 'moment'; import 'moment-plugin'; moment.foo();
例如redux的插件redux-thunk的声明文件extend-redux.d.ts是这样声明的
// node_modules/redux-thunk/extend-redux.d.ts declare module 'redux' { // declaration code...... }
总结
至此,这篇关于TypeScript声明文件的句型和场景解读的文章就介绍完了。 有关TS声明文件的更多信息,请搜索我们之前的文章或继续浏览下面的相关文章。 希望大家今后多多支持我们!
本文标题:TypeScript声明文件的句型和场景解读
本文地址: