全局声明
本教程共 80 篇 · 第 59 篇 · 更新于 2026-08-10 · 约 9 分钟阅读
本节目标:掌握
declare关键字的用法——声明全局变量、函数、类与命名空间。理解declare global如何从模块内部增强全局作用域,以及declare module "*.css"这类通配模块声明的作用。
TypeScript 的类型安全建立在”编译器知道每个值是什么类型”的基础上。但你用的不都是 TypeScript 写的代码——全局引入的 jQuery、通过 CDN 加载的第三方库、或者打包工具处理后的 CSS 和图片,TypeScript 默认都不认识。declare 关键字就是用来告诉 TypeScript:“这个变量确实存在,它的类型是这样的。“
declare 关键字的本质
declare 声明的是环境声明(ambient declaration)——它描述”存在什么”,不生成任何 JavaScript 代码。编译后,所有 declare 语句都会被删除。你可以把它理解为给 TypeScript 编译器写的”承诺书”。
// 声明一个全局变量
declare var DEBUG: boolean;
// 声明一个全局函数
declare function trackEvent(eventName: string, data?: object): void;
// 声明一个全局类
declare class UserAgent {
readonly browser: string;
readonly version: string;
}
// 声明一个全局命名空间
declare namespace AppConfig {
const apiUrl: string;
const maxRetries: number;
}
上面这些声明不会产生任何 JS 代码。但它们让 TypeScript 知道了在运行时会有 DEBUG 变量、trackEvent 函数、UserAgent 类和 AppConfig 对象——不会再报”找不到名称”的错误。
声明文件 vs 声明语句
declare 可以写在 .ts 源文件里,也可以写在 .d.ts 声明文件里。两者的区别:
// app.ts——源文件里的 declare 语句
declare var API_KEY: string;
console.log(API_KEY); // 类型安全
// globals.d.ts——声明文件,全是 declare
declare var API_KEY: string;
declare var APP_VERSION: string;
declare function log(message: string): void;
实际项目里,全局声明通常放在单独的 .d.ts 文件:一处声明,整个项目可用。混在源文件里的 declare 容易散落各地,维护起来很痛苦。
声明全局变量、函数和类
三种形式分别对应三种运行时实体:
// 变量:用 var / let / const
declare var globalConfig: {
debug: boolean;
env: "dev" | "prod";
};
declare let currentUser: { name: string } | null;
declare const MAX_SIZE: 1024;
// 函数:可以有重载
declare function $(selector: string): HTMLElement;
declare function $(selector: string, context: HTMLElement): HTMLElement;
// 类:只声明公共接口,不写实现
declare class EventBus {
on(event: string, handler: Function): void;
emit(event: string, ...args: unknown[]): void;
}
declare var / declare let / declare const 的语义和它们在常规代码里一样——const 声明的就是只读的。
declare global {}——从模块内部增强全局作用域
这是 TypeScript 里一个很精巧的设计。模块文件天然有独立作用域,但有时你需要从模块内部往全局加东西。举个例子:
// app.ts(这是一个模块,因为有顶层 export)
export function initApp() {
console.log("App started");
}
在 app.ts 里写 declare var DEBUG: boolean 会创建一个模块作用域内的声明,不会影响全局。但如果你真想给全局加一个属性,用 declare global:
// app.ts
export function initApp() {
window.myApp = { loaded: true };
}
declare global {
interface Window {
myApp: { loaded: boolean };
}
}
declare global 块里的所有声明都会作用到全局空间。上面的代码让 window.myApp 有了类型——任何文件里写 window.myApp 都不会报错。
Tip
declare global只在模块文件(有顶层 import 或 export 的文件)里有效。脚本文件本身就处于全局作用域,用不着declare global。
常见的 declare global 场景:
// 给内置类型打补丁
declare global {
interface Array<T> {
toSorted(): T[];
}
interface String {
trimStart(): string;
}
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development" | "production";
API_URL: string;
}
}
}
export {}; // 确保这个文件被当作模块
注意末尾的 export {}。declare global 需要在模块文件里才能生效,如果文件里没有 import/export,加一个空的 export {} 把文件变成模块即可。
declare module——通配模块声明
打包工具可以导入非 JS 资源——CSS、图片、JSON、SVG。这些文件 TypeScript 不认识,import styles from "./app.css" 会报类型错误。
解决方案是通配模块声明(wildcard module declaration):
// types/assets.d.ts
declare module "*.css" {
const content: Record<string, string>;
export default content;
}
declare module "*.png" {
const src: string;
export default src;
}
declare module "*.svg" {
import type { FC, SVGProps } from "react";
const Component: FC<SVGProps<SVGSVGElement>>;
export default Component;
}
这样在任何 .ts 或 .tsx 文件里导入 *.css、*.png、*.svg,TypeScript 都能给出正确的类型。
declare module 不止用于通配符。你也可以声明一个不存在的模块的类型:
declare module "virtual:config" {
const config: {
apiUrl: string;
features: string[];
};
export default config;
}
Vite 里的虚拟模块就是这样——模块不在文件系统里,由插件在运行时生成。declare module 让 TypeScript 认识了它。
让 declare 文件被 TypeScript 发现
全局 .d.ts 文件需要被 TypeScript 纳入编译范围。TS 7.0 默认 types: [],意味着不会自动加载 node_modules/@types/*。你需要显式配置:
{
"compilerOptions": {
"types": ["node"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts"]
}
项目自己的 .d.ts 文件放在 src 目录下,通过 include 字段让编译器找到。@types/* 包通过 types 字段逐个引入——这和 TS 6.x 以前”自动加载所有 @types” 的行为不一样,是 7.0 的破坏性变更之一。
小结
declare 是 TypeScript 和外部 JavaScript 世界对话的桥梁。声明全局变量、函数、类让编译器认识那些不在 TypeScript 代码里的东西;declare global 从模块内部扩展全局作用域;declare module 让非 JS 资源和虚拟模块也有类型支持。这些声明最终都不会出现在编译产物里——它们纯粹是给编译器看的元信息。