declare 关键字
1729字约6分钟
TypeScript
2023-02-01
declare
关键字用来告诉编译器,某个类型是存在的,可以在当前文件中使用。
它的主要作用,就是让当前文件可以使用其他文件声明的类型。
举例来说,自己的脚本使用外部库定义的函数,编译器会因为不知道外部函数的类型定义而报错,这时就可以在自己的脚本里面使用declare
关键字,告诉编译器外部函数的类型。这样的话,编译单个脚本就不会因为使用了外部类型而报错。
declare 关键字可以描述以下类型。
- 变量(const、let、var 命令声明)
- type 或者 interface 命令声明的类型
- class
- enum
- 函数(function)
- 模块(module)
- 命名空间(namespace)
declare
关键字的重要特点是,它只是通知编译器某个类型是存在的,不用给出具体实现。比如,只描述函数的类型,不给出函数的实现,如果不使用declare
,这是做不到的。
declare
只能用来描述已经存在的变量和数据结构,不能用来声明新的变量和数据结构。另外,所有declare
语句都不会出现在编译后的文件里面。
✨declare 变量
下面的例子是脚本使用浏览器全局对象document
。
declare var document;
document.title = "Hello";
上面示例中,declare
告诉编译器,变量document
的类型是外部定义的(具体定义在 TypeScript 内置文件lib.d.ts
)。
注意,declare
关键字只用来给出类型描述,是纯的类型代码,不允许设置变量的初始值,即不能涉及值。
// 报错
declare let x: number = 1;
✨declare 函数
declare
关键字可以给出外部函数的类型描述。
declare function sayHello(name: string): void;
sayHello("张三");
注意,这种单独的函数类型声明语句,只能用于declare
命令后面。一方面,TypeScript 不支持单独的函数类型声明语句;另一方面,declare
关键字后面也不能带有函数的具体实现。
// 报错
function sayHello(name: string): void;
function sayHello(name) {
return "你好," + name;
}
✨declare class
declare
给出 class
的描述描述写法如下。
同样的,declare
后面不能给出 Class
的具体实现或初始值。
declare class C {
// 静态成员
public static s0(): string;
private static s1: string;
// 属性
public a: number;
private b: number;
// 构造函数
constructor(arg: number);
// 方法
m(x: number, y: number): number;
// 存取器
get c(): number;
set c(value: number);
// 索引签名
[index: string]: any;
}
✨declare module,declare namespace
如果想把变量、函数、类组织在一起,可以将 declare
与 module
或 namespace
一起使用。
declare module
和 declare namespace
里面,加不加 export
关键字都可以。
declare namespace AnimalLib {
class Animal {
constructor(name: string);
eat(): void;
sleep(): void;
}
type Animals = "Fish" | "Dog";
}
// 或者
declare module AnimalLib {
class Animal {
constructor(name: string);
eat(): void;
sleep(): void;
}
type Animals = "Fish" | "Dog";
}
declare
关键字的另一个用途,是为外部模块添加属性和方法时,给出新增部分的类型描述。
import { Foo as Bar } from "moduleA";
declare module "moduleA" {
interface Bar extends Foo {
custom: {
prop1: string;
};
}
}
上面示例中,从模块moduleA
导入了Foo
接口,将其重命名为Bar
,并用 declare
关键字为Bar
增加一个属性custom
。
下面是另一个例子。一个项目有多个模块,可以在一个模型中,对另一个模块的接口进行类型扩展。
// a.ts
export interface A {
x: number;
}
// b.ts
import { A } from "./a";
declare module "./a" {
interface A {
y: number;
}
}
const a: A = { x: 0, y: 0 };
使用这种语法进行模块的类型扩展时,有两点需要注意:
(1) declare module NAME
语法里面的模块名NAME
,跟 import
和 export
的模块名规则是一样的,且必须跟当前文件加载该模块的语句写法(上例import { A } from './a'
)保持一致。
(2) 不能创建新的顶层类型。也就是说,只能对a.ts
模块中已经存在的类型进行扩展,不允许增加新的顶层类型,比如新定义一个接口B
。
(3) 不能对默认的default
接口进行扩展,只能对 export
命令输出的命名接口进行扩充。这是因为在进行类型扩展时,需要依赖输出的接口名。
某些第三方模块,原始作者没有提供接口类型,这时可以在自己的脚本顶部加上下面一行命令。
declare module "模块名";
// 例子
declare module "hot-new-module";
加上上面的命令以后,外部模块即使没有类型,也可以通过编译。但是,从该模块输入的所有接口都将为any
类型。
declare module
描述的模块名可以使用通配符。
declare module "my-plugin-*" {
interface PluginOptions {
enabled: boolean;
priority: number;
}
function initialize(options: PluginOptions): void;
export = initialize;
}
上面示例中,模块名my-plugin-*
表示适配所有以my-plugin-
开头的模块名(比如my-plugin-logger
)。
✨declare global
如果要为 JavaScript 引擎的原生对象添加属性和方法,可以使用declare global {}
语法。
export {};
declare global {
interface String {
toSmallString(): string;
}
}
String.prototype.toSmallString = (): string => {
// 具体实现
return "";
};
上面示例中,为 JavaScript 原生的String
对象添加了toSmallString()
方法。declare global
给出这个新增方法的类型描述。
这个示例第一行的空导出语句export {}
,作用是强制编译器将这个脚本当作模块处理。这是因为declare global
必须用在模块里面。
下面的示例是为 window
对象添加一个属性myAppConfig
。
export {};
declare global {
interface window {
myAppConfig: object;
}
}
const config = window.myAppConfig;
declare global
只能扩充现有对象的类型描述,不能增加新的顶层类型。
✨declare enum
declare
关键字给出 enum
类型描述的例子如下,下面的写法都是允许的。
declare enum E1 {
A,
B,
}
declare enum E2 {
A = 0,
B = 1,
}
declare const enum E3 {
A,
B,
}
declare const enum E4 {
A = 0,
B = 1,
}
✨declare module 用于类型声明文件
我们可以为每个模块脚本,定义一个.d.ts
文件,把该脚本用到的类型定义都放在这个文件里面。
但是,更方便的做法是为整个项目,定义一个大的.d.ts
文件,在这个文件里面使用declare module
定义每个模块脚本的类型。
下面的示例是node.d.ts
文件的一部分。
declare module "url" {
export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
}
export function parse(
urlStr: string,
parseQueryString?,
slashesDenoteHost?
): Url;
}
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export var sep: string;
}
上面示例中,url
和path
都是单独的模块脚本,但是它们的类型都定义在node.d.ts
这个文件里面。
使用时,自己的脚本使用三斜杠命令,加载这个类型声明文件。
/// <reference path="node.d.ts"/>
如果没有上面这一行命令,自己的脚本使用外部模块时,就需要在脚本里面使用 declare
命令单独给出外部模块的类型。