目录
简介
语法
内容
模块化
模块语法
三斜线指令
reference
amd-module
场景

  1. 在外部我的项目中给外部我的项目写申明文件
  2. 给第三方包写申明文件
    全局变量的第三方库
    批改全局变量的模块的第三方库的申明
    批改window
    ESM和CommonJS
    UMD
    模块插件
    总结

简介
申明文件是以.d.ts为后缀的文件,开发者在申明文件中编写类型申明,TypeScript依据申明文件的内容进行类型查看。(留神同目录下最好不要有同名的.ts文件和.d.ts,例如lib.ts和lib.d.ts,否则模块零碎无奈只依据文件名加载模块)

为什么须要申明文件呢?咱们晓得TypeScript依据类型申明进行类型查看,但有些状况可能没有类型申明:

第三方包,因为第三方包打包后都是JavaScript语法,而非TypeScript,没有类型。
宿主环境扩大,如一些hybrid环境,在window变量下有一些bridge接口,这些接口没有类型申明。
如果没有类型申明,在应用变量、调用函数、实例化类的时候就没法通过TypeScript的类型查看。

申明文件就是针对这些状况,开发者在申明文件中编写第三方模块的类型申明/宿主环境的类型申明。让TypeScript能够失常地进行类型查看。

除此之外,申明文件也能够被导入,应用其中裸露的类型定义。

总之,申明文件有两种用法:

被通过import导入,应用其中裸露的类型定义和变量申明。
和相干模块关联,为模块进行类型申明。
对于第二种用法,申明文件如何同相干模块关联呢?

比方有个第三方包名字叫"foo",那么TypeScript会在node_modules/foo中依据其package.json的types和typing字段查找申明文件查找到的申明文件被作为该模块的申明文件;TypeScript也会在node_modules/@types/foo/目录中查找申明文件,如果能找到就被作为foo模块的申明文件;TypeScript还会在咱们的我的项目中查找.d.ts文件,如果遇到declare module 'foo'语句,则该申明被用作foo模块的申明。

总结一下,TypeScript会在特定的目录读取指定的申明文件。

在外部我的项目中,TypeScript会读取tsconfig.json中的文件汇合,在其中的申明文件才会被解决。
读取node_modules中各第三方包的package.json的types或者typing指定的文件。
读取@types目录下同名包的申明文件。
申明文件中的代码不会呈现在最终的编译后果中,编译后会把转换后的JavaScript代码输入到"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,否则会被认为存在初始化的内容,从而报错。

// foo.d.tslet a: number = 1; // error TS1039: Initializers are not allowed in ambient contexts.

declare也容许呈现在.ts文件中,但个别不会这么做,.ts文件中间接用let/const/function/class就能够申明并初始化一个变量。并且.ts文件编译后也会去掉declare的语句,所以不须要declare语句。

留神,declare多个同名的变量是会抵触的

declare let foo: number; // error TS2451: Cannot redeclare block-scoped variable 'a'. declare let foo: number; // error TS2451: Cannot redeclare block-scoped variable 'a'.

除了应用declare申明一个值,declare还能够用来申明一个模块和全局的插件,这两种用法都是在特定场景用来给第三方包做申明。

declare module用来给一个第三方模进行类型申明,比方有一个第三方包foo,没有类型申明。咱们能够在咱们我的项目中实现一个申明文件来让TypeScript能够辨认模块类型:foo.d.ts

// foo.d.tsdeclare module 'foo' {    export let size: number;}

而后咱们就能够应用了:

import foo from 'foo'; console.log(foo.size);

eclare module除了能够用来给一个模块申明类型,还能够用来实现模块插件的申明。前面大节中会做介绍。

declare global用来给扩大全局的第三方包进行申明,前面大节介绍。

模块化

模块语法
申明文件的模块化语法和.ts模块的相似,在一些细节上稍有不同。.ts导出的是模块(typescript会依据导出的模块判断类型),.d.ts导出的是类型的定义和申明的值。

申明文件能够导出类型,也能够导出值的申明

// index.d.ts // 导出值申明export let a: number; // 导出类型export interface Person {    name: string;};

申明文件能够引入其余的申明文件,甚至能够引入其余的.ts文件(因为.ts文件也可能导出类型)

// Person.d.tsexport default interface Person {name: string} // index.d.tsimport Person from './person'; export let p: Person;

如果申明文件不导出,默认是全局能够拜访的

// person.d.tsinterface Person {name: string}declare let p: Person; // index.tslet p1: Person = {name: 'Sam'};console.log(p);

如果应用模块导出语法(ESM/CommJS/UMD),则不解析为全局(当然UMD还是能够全局拜访)。

// ESM interface Person {name: string} export let p: Person; export default Person;
// CommonJSinterface Person {name: string} declare let p: Person; export = p;
// UMDinterface Person {name: string} declare let p: Person; export = p;export as namespace p;

留神:UMD包export as namespace语法只能在申明文件中呈现。

三斜线指令
申明文件中的三斜线指令,用于管制编译过程。

三斜线指令仅可放在蕴含它的文件的最顶端。

如果指定--noResove编译选项,预编译过程会疏忽三斜线指令。

reference
reference指令用来表明申明文件的依赖状况。

/// <reference path="..." />用来通知编译器依赖的其余申明文件。编译器预处理时候会将path指定的申明文件退出进来。门路是绝对于文件本身的。援用不存在的文件或者援用本身,会报错。

/// <reference types="node" />用来通知编译器它依赖node_modules/@types/node/index.d.ts。如果你的我的项目外面依赖了@types中的某些申明文件,那么编译后输入的申明文件中会主动加上这个指令,用以阐明你的我的项目中的申明文件依赖了@types中相干的申明文件。

/// <reference no-default-lib="true"/>,

这波及两个编译选项,--noLib,设置了这个编译选项后,编译器会疏忽默认库,默认库是在装置TypeScript时候主动引入的,这个文件蕴含 JavaScript 运行时(如window)以及 DOM 中存在各种常见的环境申明。然而如果你的我的项目运行环境和基于规范浏览器运行时环境有很大不同,可能须要排除默认库,一旦你排除了默认的 lib.d.ts 文件,你就能够在编译上下文中蕴含一个命名类似的文件,TypeScript 将提取该文件进行类型查看。

另一个编译选项是--skipDefaultLibCheck这个选项会让编译器疏忽蕴含了/// <reference no-default-lib="true"/>指令的申明文件。你会留神到在默认库的顶端都会有这个三斜线指令,因而如果采纳了--skipDefaultLibCheck编译选项,也同样会疏忽默认库。

amd-module
amd-module相干指令用于管制打包到amd模块的编译过程

///<amd-module name='NamedModule'/>这个指令用于通知编译器给打包为AMD的模块传入模块名(默认状况是匿名的)

///<amd-module name='NamedModule'/>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开发的我的项目可能更好地应用你的库,那么你就须要写好你的申明文件。

这两种状况的申明文件的语法相似,只在个别申明语法和文件的解决上有区别:

外部我的项目给第三方包写申明文件时候,以.d.ts命名即可,而后在tsconfig.json中的files和include中配置可能蕴含到文件即可,内部模块的申明文件须要打包到输入目录,并且在package.json中的type字段指定申明文件地位;或者上传到@types/<moduleName>中,使用者通过npm install @types/<moduleName>装置申明文件。redux就在tsconfig.json中指定了declarationDir为./types,TypeScript会将我的项目的申明都打包到这个目录下,目录构造和源码一样,而后redux源码入口处导出了所有的模块,因而types目录下也有一个入口的申明文件index.d.ts,并且蕴含了所有的导出模块申明,redux在package.json中指定types字段(或者typings字段)为入口的申明文件:./types/index.d.ts。这样就实现了主动生成接口的申明文件。
外部我的项目给第三方写申明文件时候,如果是通过npm模块引入形式,如import moduleName from 'path';则须要通过declare module '<moduleName>'语法来申明模块。而内部模块的申明文件都是失常的类型导出语法(如export default export =等),如果申明文件在@types中,会将与模块同名的申明文件作为模块的类型申明;如果申明文件在第三方包中,那么就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;

依据接口的申明合并原理,插件模块能够这样申明

interface Person {    // 扩大了age属性    age: number;}

下面的全局模块的插件模块的申明形式能够利用于上面的场景:

外部我的项目应用了插件,但插件没有申明文件,咱们能够在外部我的项目中本人实现申明文件。
给插件模块写申明文件并公布到@types。
如果是插件模块的作者,心愿在我的项目中援用全局模块并且将扩大的类型输入到申明文件,以便其余我的项目应用。能够这样实现

// plugin/index.ts // 留神这样申明才会让TypeScript将类型输入申明文件declare global {    // 假如全局模块应用全局变量的形式申明    interface Person {        age: number    }} console.log(person.age); export {};

留神,declare global写在申明文件中也能够,然而要在尾部加上export {}或者其余的模块导出语句,否则会报错。另外declare global在申明文件中写的话,编译后不会输入到申明文件中。

批改window
window的类型是interface Window {...},在默认库中申明,如果要扩大window变量(如一些hybrid环境)能够这样实现

// 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 module实现

declare module 'person' {    export let name: string;}

UMD
UMD模块,在CommonJS申明的根底上加上export as namespace ModuleName;语句即可。

看上面的ESM的例子

// node_modules/@types/person/index.d.tsinterface Person {    name: string;} declare let person: Person; export default person; export as namespace person;

能够通过import导入来拜访

// src/index.tsimport 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......}