- 新语法索引
- 什么是声明语句
- 什么是声明文件
- 书写声明文件
- 全局变量
declare functiondeclare class- §">
declare enum§ - §">
declare namespace§ interface和type
- npm 包
- UMD 库
- 全局变量
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
新语法索引
declare var声明全局变量declare function声明全局方法declare class声明全局类declare enum声明全局枚举类型declare namespace声明(含有子属性的)全局对象interface和type声明全局类型export导出变量export namespace导出(含有子属性的)对象export defaultES6 默认导出export =commonjs 导出模块export as namespaceUMD 库声明全局变量declare global扩展全局变量declare module扩展模块/// <reference />三斜线指令
什么是声明语句
假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script> 标签引入 jQuery,然后就可以使用全局变量 $ 或 jQuery 了。
但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西:
jQuery('#foo');// ERROR: Cannot find name 'jQuery'.
这时,我们需要使用 declare var 来定义它的类型:
declare var jQuery: (selector: string) => any;jQuery('#foo');
上例中,declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:
jQuery('#foo');
除了 declare var 之外,还有其他很多种声明语句,将会在后面详细介绍。
什么是声明文件
通常我们会把声明语句放到一个单独的文件(jQuery.d.ts)中,这就是声明文件:
// src/jQuery.d.tsdeclare var jQuery: (selector: string) => any;
// src/index.tsjQuery('#foo');
声明文件必需以 .d.ts 为后缀。
第三方声明文件
我们可以直接下载下来使用,但是更推荐的是使用 @types 统一管理第三方库的声明文件。@types 的使用方式很简单,直接用 npm 安装对应的声明模块即可,以 jQuery 举例:
npm install @types/jquery --save-dev
可以在这个页面搜索你需要的声明文件。(https://www.typescriptlang.org/dt/search?search=)
书写声明文件
当一个第三方库没有提供声明文件时,我们就需要自己书写声明文件了。前面只介绍了最简单的声明文件内容,而真正书写一个声明文件并不是一件简单的事,以下会详细介绍如何书写声明文件。
在不同的场景下,声明文件的内容和使用方式会有所区别。
库的使用场景主要有以下几种:
- 全局变量:通过
<script>标签引入第三方库,注入全局变量 - npm 包:通过
import foo from 'foo'导入,符合 ES6 模块规范 - UMD 库:既可以通过
<script>标签引入,又可以通过import导入 - 直接扩展全局变量:通过
<script>标签引入后,改变一个全局变量的结构 - 在 npm 包或 UMD 库中扩展全局变量:引用 npm 包或 UMD 库后,改变一个全局变量的结构
- 模块插件:通过
<script>或import导入后,改变另一个模块的结构
全局变量
全局变量是最简单的一种场景,之前举的例子就是通过 <script> 标签引入 jQuery,注入全局变量 $ 和 jQuery。如果没有生效,查看教程:
https://ts.xcatliu.com/basics/declaration-files.html#npm-bao
全局变量的声明文件主要有以下几种语法:
declare var声明全局变量declare function声明全局方法declare class声明全局类declare enum声明全局枚举类型declare namespace声明(含有子属性的)全局对象interface和type声明全局类型
全局变量声明类中:
// src/jQuery.d.tsdeclare let jQuery: (selector: string) => any;
类的使用中:
// src/index.tsjQuery('#foo');// 使用 declare let 定义的 jQuery 类型,允许修改这个全局变量jQuery = function(selector) {return document.querySelector(selector);};
而当我们使用 const 定义时,表示此时的全局变量是一个常量,不允许再去修改它的值了:
一般来说,全局变量都是禁止修改的常量,所以大部分情况都应该使用 const 而不是 var 或 let。
需要注意的是,声明语句中只能定义类型,切勿在声明语句中定义具体的实现:
declare const jQuery = function(selector) {return document.querySelector(selector);};// ERROR: An implementation cannot be declared in ambient contexts.
declare function
declare function 用来定义全局函数的类型。jQuery 其实就是一个函数,所以也可以用 function 来定义:
// src/jQuery.d.tsdeclare function jQuery(selector: string): any;
// src/index.tsjQuery('#foo');
// src/jQuery.d.tsdeclare function jQuery(selector: string): any;declare function jQuery(domReadyCallback: () => any): any;
// src/index.tsjQuery('#foo');jQuery(function() {alert('Dom Ready!');});
declare class
当全局变量是一个类的时候,我们用 declare class 来定义它的类型:
// src/Animal.d.tsdeclare class Animal {name: string;constructor(name: string);sayHi(): string;}
// src/index.tslet cat = new Animal('Tom');
同样的,declare class 语句也只能用来定义类型,不能用来定义具体的实现,比如定义 sayHi 方法的具体实现则会报错:
// src/Animal.d.tsdeclare class Animal {name: string;constructor(name: string);sayHi() {return `My name is ${this.name}`;};// ERROR: An implementation cannot be declared in ambient contexts.}
declare enum§
使用 declare enum 定义的枚举类型也称作外部枚举(Ambient Enums),举例如下:
// src/Directions.d.tsdeclare enum Directions {Up,Down,Left,Right}
// src/index.tslet directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
与其他全局变量的类型声明一致,declare enum 仅用来定义类型,而不是具体的值。Directions.d.ts 仅仅会用于编译时的检查,声明文件里的内容在编译结果中会被删除。它编译结果是:
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
其中 Directions 是由第三方库定义好的全局变量。
declare namespace§
namespace 是 ts 早期时为了解决模块化而创造的关键字,中文称为命名空间。namespace 被淘汰了,但是在声明文件中,declare namespace 还是比较常用的,它用来表示全局变量是一个对象,包含很多子属性。
嵌套的命名空间
如果对象拥有深层的层级,则需要用嵌套的 namespace 来声明深层的属性的类型:
// src/jQuery.d.tsdeclare namespace jQuery {function ajax(url: string, settings?: any): void;namespace fn {function extend(object: any): void;}}
// src/index.tsjQuery.ajax('/api/get_something');jQuery.fn.extend({check: function() {return this.each(function() {this.checked = true;});}});
interface 和 type
除了全局变量之外,可能有一些类型我们也希望能暴露出来。在类型声明文件中,我们可以直接使用 interface 或 type 来声明一个全局的接口或类型:
// src/jQuery.d.tsinterface AjaxSettings {method?: 'GET' | 'POST'data?: any;}declare namespace jQuery {function ajax(url: string, settings?: AjaxSettings): void;}
这样的话,在其他文件中也可以使用这个接口或类型了:
// src/index.tslet settings: AjaxSettings = {method: 'POST',data: {name: 'foo'}};jQuery.ajax('/api/post_something', settings);
type 与 interface 类似,不再赘述。
防止命名冲突
暴露在最外层的 interface 或 type 会作为全局类型作用于整个项目中,我们应该尽可能的减少全局变量或全局类型的数量。故最好将他们放到 namespace 下:
// src/jQuery.d.tsdeclare namespace jQuery {interface AjaxSettings {method?: 'GET' | 'POST'data?: any;}function ajax(url: string, settings?: AjaxSettings): void;}
注意,在使用这个 interface 的时候,也应该加上 jQuery 前缀:
// src/index.tslet settings: jQuery.AjaxSettings = {method: 'POST',data: {name: 'foo'}};jQuery.ajax('/api/post_something', settings);
npm 包
npm 包的声明文件主要有以下几种语法:
export导出变量export namespace导出(含有子属性的)对象export defaultES6 默认导出export =commonjs 导出模块(不常用)
在 commonjs 规范中,我们用以下方式来导出一个模块:
// 整体导出module.exports = foo;// 单个导出exports.bar = bar;
在 ts 中,针对这种模块导出,有多种方式可以导入,第一种方式是 const ... = require:
// 整体导入const foo = require('foo');// 单个导入const bar = require('foo').bar;
第二种方式是 import ... from,注意针对整体导出,需要使用 import * as 来导入:
// 整体导入import * as foo from 'foo';// 单个导入import { bar } from 'foo';
第三种方式是 import ... require,这也是 ts 官方推荐的方式:
// 整体导入import foo = require('foo');// 单个导入import bar = foo.bar;
UMD 库
既可以通过 <script> 标签引入,又可以通过 import 导入的库,称为 UMD 库。
export as namespace
一般使用 export as namespace 时,都是先有了 npm 包的声明文件,再基于它添加一条 export as namespace 语句,即可将声明好的一个变量声明为全局变量
