在 TypeScript 中,declare 关键字用于告诉编译器有关类型或变量的信息,而不是实际提供实现。 它允许我们在 TypeScript 中使用一些没有具体实现的东西,比如全局变量、函数、类等。 Declare 关键字通常与声明文件(.d.ts)一起使用,声明文件用于描述已存在的 JavaScript 代码的类型信息。
declare 仅描述类型,不生成任何 JavaScript 代码。
declare 关键字可以描述以下类型。
- 变量(const、let、var 命令声明)
- type 或者 interface 命令声明的类型
- class
- enum
- 函数(function)
- 模块(module)
- 命名空间(namespace)
1.仅类型,无实现
declare
声明的类不能包含具体实现(如方法体、属性初始化)。- 例如:以下代码会报错:
declare class world { m() { console.log("Hello world"); } // ❌ 错误:declare 类不能包含实现 }
2.必须与其他声明合并
- 如果实际代码中存在同名类,
declare
声明会与其合并,补充类型信息。// 在 JavaScript 中已有类 world class world { /* ... */ } // 在 TypeScript 中补充类型声明 declare class world { public a: string; private b: string; }
一、声明全局变量/函数/类
案例一:声明全局变量
declare var document: Document;
document.title = "Hello World"; // 使用浏览器全局对象
上面示例中,declare 告诉编译器,变量document
的类型是外部定义的(具体定义在 TypeScript 内置文件lib.d.ts
)。
注意,declare 关键字只用来给出类型描述,是纯的类型代码,不允许设置变量的初始值,即不能涉及值。如: declare let x:number = 1; //报错
案例二:声明全局函数
declare function logMessage(message: string): void;
logMessage("Debug Info"); // 调用外部脚本定义的函数
注意,这种单独的函数类型声明语句,只能用于
declare
命令后面。一方面,TypeScript 不支持单独的函数类型声明语句;另一方面,declare 关键字后面也不能带有函数的具体实现。
案例三:声明全局类
declare class C {
// 静态成员(类级别)
public static s0(): string; // 公共静态方法,可通过 C.s0() 直接调用,返回字符串。
private static s1: string; // 私有静态属性,仅类内部可访问
// 实例属性
public a: number; // 公共数字类型属性
private b: number; // 私有数字类型属性,仅类内部可访问
// 构造函数
constructor(arg: number); // 接受一个 number 参数,实例化时必须传入,如 new C(123)。
// 实例方法
m(x: number, y: number): number; // 接受两个数字参数,返回数字的方法
// 存取器(访问器)
get c(): number; // 获取属性c的存取器
set c(value: number); // 设置属性c的存取器
// 索引签名
[index: string]: any; // 允许通过字符串索引访问任意类型属性
}
上述例子的用法
const obj = new C(123);
obj.a = 456; // 合法(public 属性)
console.log(C.s0()); // 合法(静态方法)
obj.m(1, 2); // 调用实例方法
obj.c = 789; // 调用 setter
obj['dynamicProp'] = "Hello"; // 合法(索引签名允许 any 类型)
这里做一下拓展:什么是静态成员,什么实例成员
在这个TypeScript类声明中,static
表明这两个成员是类级别的(静态成员),而不是实例级别的。也就是说,它们不需要创建类的实例就可以访问
主要特点:
- 类级别访问:通过类名直接访问(如
C.s0()
),无需实例化 - 共享存储:所有实例共享同一个静态成员
- 生命周期:在类加载时初始化,伴随整个程序生命周期
- 访问限制:可以与访问修饰符结合使用(如示例中的private静态属性)
例子
// 静态成员调用方式
C.s0(); // ✅ 正确
new C().s0(); // ❌ 错误
// 实例成员调用方式
const c = new C();
c.m(1,2); // ✅ 正确
C.m(1,2); // ❌ 错误
总结
特性 | 静态成员 | 实例成员 |
---|---|---|
归属对象 | 类 | 类的实例 |
内存分配 | 全局唯一 | 每个实例独立 |
访问方式 | C.s0() |
new C().s0(); |
典型用途 | 工具方法、共享状态 | 对象状态、行为 |
能否访问实例成员 | ❌ 不能(除非通过对象实例) | ✅ 能 |
二、声明模块类型
案例:为第三方 JavaScript 库 my-legacy-lib
添加类型声明
// types/my-legacy-lib.d.ts
declare module 'my-legacy-lib' {
// 导出函数
export function calculate(a: number, b: number): number;
// 导出命名空间(嵌套类型)
export namespace Utils {
export function log(message: string): void;
}
}
// 声明全局变量
declare const LIB_VERSION: string;
在 TypeScript 代码中使用
import { calculate, Utils } from 'my-legacy-lib';
// 使用声明的类型
const sum = calculate(3, 5); // 类型检查:参数必须为 number
Utils.log(`Result: ${sum}`); // 类型检查:参数必须为 string
// 使用全局变量
console.log(LIB_VERSION); // 类型为 string
为现有模块添加新类型
// 扩展原模块
declare module 'my-legacy-lib' {
// 新增接口
export interface Config {
debug: boolean;
timeout: number;
}
// 新增函数
export function setConfig(config: Config): void;
}
三、命名空间与类型合并
组织复杂类型或扩展已有接口(如为 Array
添加自定义方法)。
// 声明命名空间
declare namespace MyLib {
interface Config {
timeout: number;
}
function fetch(url: string, config?: Config): Promise<Response>;
}
const config: MyLib.Config = { timeout: 5000 }; // 使用命名空间中的类型
// 扩展 Array 类型
declare global {
interface Array<T> {
shuffle(): T[];
}
}
[1, 2, 3].shuffle(); // 调用新增方法
上述代码中declare namespace
用于逻辑分组,declare global
用于全局扩展,需在模块文件中使用 export {}
避免作用域冲突
四、为 JavaScript 引擎的原生对象添加属性和方法
如果要为 JavaScript 引擎的原生对象添加属性和方法,可以使用declare global {}
语法。
export {};
declare global {
interface String {
toSmallString(): string;
}
}
String.prototype.toSmallString = ():string => {
// 具体实现
return '';
};
上面示例中,为 JavaScript 原生的String
对象添加了toSmallString()
方法。declare global 给出这个新增方法的类型描述。
这个示例第一行的空导出语句export {}
,作用是强制编译器将这个脚本当作模块处理。这是因为declare global
必须用在模块里面。
五、特殊用法:声明枚举与类型别名
案例:描述外部脚本定义的枚举或复杂类型。
// 声明枚举
declare enum LogLevel {
DEBUG,
ERROR,
}
// 声明类型别名
declare type ApiResponse<T> = {
data: T;
code: number;
};
上述代码中 declare enum
和 declare type
适用于类型已存在但需显式声明的情况,避免重复定义