关于javascript:TypeScript声明文件的语法与场景详解

5次阅读

共计 8378 个字符,预计需要花费 21 分钟才能阅读完成。

目录
简介
语法
内容
模块化
模块语法
三斜线指令
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.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 多个同名的变量是会抵触的

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.ts
declare 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.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 包 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.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
 
// 留神如果用 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......}
正文完
 0