我的编程空间,编程开发者的网络收藏夹
学习永远不晚

TypeScript声明文件的语法与场景详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

TypeScript声明文件的语法与场景详解

简介

声明文件是以.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);

declare 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导入来访问

// class="lazy" data-src/index.ts
import person from 'person';

console.log(person.name);

也可以通过全局访问

// class="lazy" data-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引入访问

// class="lazy" data-src/index.ts

import person from 'person';

console.log(person.name);

也可以全局访问

// class="lazy" data-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;
}

// class="lazy" data-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声明文件内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

TypeScript声明文件的语法与场景详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

详解Go语言中获取文件路径的不同方法与应用场景

在Go语言中,获取文件路径的方法有多种,每种都有其特定的应用场景。本文详细介绍了使用os.Getwd()获取当前工作目录、使用filepath.Abs()转换相对路径、使用filepath.Join()连接路径片段、使用io/ioutil.TempDir()创建临时目录、使用filepath.Dir()提取目录路径和使用filepath.Base()提取文件名等方法及其应用场景,旨在帮助开发者根据实际需要选择合适的方法进行文件路径操作。
详解Go语言中获取文件路径的不同方法与应用场景
2024-04-02

GO语言函数(func)的声明与使用详解

这篇文章主要介绍了GO函数(func)的声明与使用,包括了GO语言函数声明与使用,GO语言递归函数,GO语言内置函数,GO语言函数defer应用,GO语言函数可变长度参数需要的朋友可以参考下
2022-12-20

Dart语法之变量声明与数据类型实例详解

这篇文章主要为大家介绍了Dart语法之变量声明与数据类型实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

重学Go语言之变量与常量的声明与使用详解

变量、常量的声明与使用是掌握一门编程语言的基础,在这篇文章中,小编就来带大家学习一下Go语言是怎么样声明和使用变量与常量吧
2023-03-02

C++ 函数声明的详细语法:从语法解析到规范用法解析

c++++函数声明语法为:returntype functionname(parametertype1 parametername1, ... , parametertypen parameternamen);,其中returntype为返
C++ 函数声明的详细语法:从语法解析到规范用法解析
2024-04-30

详解Go语言中init的使用与常见应用场景

Go语言中的init函数在程序启动时自动执行,可用于包级初始化、资源管理、注册插件等。它不接受参数,也不返回值。通过init函数,开发者可在程序启动时执行必要的初始化,确保依赖关系正确初始化,并实现顺序或并行初始化。遵循最佳实践,如保持简洁、避免依赖、处理错误和使用defer,可有效使用init函数。
详解Go语言中init的使用与常见应用场景
2024-04-02

Go语言方法与函数的区别及应用场景解析

go语言方法与函数的区别在于与结构体的关联性:方法与结构体关联,用于操作结构体数据或方法;函数独立于类型,用于执行通用操作。Go语言方法与函数的区别及应用场景解析在Go语言中,方法和函数是两个 estrechamente 相关的概念,它们
Go语言方法与函数的区别及应用场景解析
2024-04-04

CSS行内元素与块级元素的使用场景和方法详解

CSS行内元素和块级元素详解:探索它们的应用场景和使用方法在CSS中,元素可以根据其显示特性分为两种类型:行内元素和块级元素。对于网页开发者来说,理解这两个概念非常重要,因为它们的不同特性决定了它们的应用场景和使用方法。行内元素行内元素是指
CSS行内元素与块级元素的使用场景和方法详解
2023-12-23

MyBatis映射文件中parameterType与resultType的用法详解

MyBatis中的ParameterType指的是SQL语句中的参数类型,即传入SQL语句中的参数的类型,下面这篇文章主要给大家介绍了关于MyBatis映射文件中parameterType与resultType用法的相关资料,需要的朋友可以参考下
2023-05-15

CFile与CStdioFile的文件读写使用方法详解

以下是对CFile与CStdioFile的文件读写使用方法进行了详细的分析介绍,需要的朋友可以过来参考下
2022-11-15

编程热搜

目录