作为 TypeScript 开发者,你有没有在某个外部库的类型定义文件中见过这样的代码:
declare var $: any;
declare function myLibrary(param: string): void;
或许看到 declare
的第一反应可能是:“这是什么奇怪的写法?和普通变量或函数声明有啥区别?”
哦豁\~如果你也感到困惑,这篇文章就是为你准备的,让你彻底告别对它的误解!
当然如果你的反应是:就这?也建议你往下看看,权当是巩固一下基础,亦或者看看是不是要打脸?!!
一、什么是 declare
?
在 TypeScript 中,declare
是一个专门用来声明的关键字,它的核心作用是告诉 TypeScript:“有这个东西,但别管它的实现。”
换句话说,declare
是用来为外部或未实现的实体提供类型提示的。
- 它的声明只存在于编译阶段,不会被编译成 JavaScript 代码 。
- 常用于处理全局变量 、动态加载模块 或外部库的类型定义 。
二、declare
的常见用法
1. 声明全局变量或函数等
在现代前端开发中,使用第三方 JavaScript 库是家常便饭。以历史著名的 jQuery 为例,其核心的全局变量 $
是我们操作 DOM 的得力工具。然而,在 TypeScript 项目中,如果不进行特殊处理,编译器会对 $
的使用感到困惑。
所以当你需要使用一个全局变量或者全局函数,但没有实际的类型定义时 ,可以使用 declare
告诉 TypeScript:
declare var $: (selector: string) => any;
declare function alert(message: string): void;
// 使用
$('#id').show(); // TypeScript 不再报错
// 使用
alert('Hello, TypeScript!');
注意:这里的 declare var
并不会在运行时生成任何 JavaScript 代码,只是为了静态检查。
亦或者是:在项目初期,你可能需要先声明某些全局变量或函数,后续再补充实现。例如:
// 声明
declare function fetchData(): Promise<string>;
运行时,实际的实现可能是这样:
// 由某些后续业务代码或库提供实现
function fetchData() {
return Promise.resolve('Hello, world!');
}
declare
关键字可以用来声明:
- const、let、var
- type、interface
- class
- enum
- function
- module
- namespace
2. 声明外部模块
在使用没有 TypeScript 类型定义的外部库时(如某些老旧的 JavaScript 库),declare
可以为模块提供临时的类型提示:
declare module 'some-library' {
export function doSomething(): void;
}
在代码中使用 some-library
时,就不会再有类型报错:
import { doSomething } from 'some-library';
doSomething();
3. 声明类型文件(.d.ts
)
declare
的最常见场景之一就是在类型声明文件(.d.ts
)中为 TypeScript 提供类型信息。
声明文件是专门用于存放declare
声明的特殊文件,其扩展名为 .d.ts
。它与普通的 .ts
文件有着明显的区别,普通 .ts
文件包含实际的代码逻辑和类型声明,会被编译成 JavaScript 文件,而声明文件仅提供类型信息,不会生成 JavaScript 代码。
一般在项目中使用的声明类型文件:
- 在大型项目中,往往会引入多个第三方库 ,每个库可能都有自己的全局变量。使用 declare 关键字统一对这些全局变量进行类型声明,可以让代码结构更加清晰有序。例如,我们可以创建一个专门的
library.d.ts
文件,将所有第三方库的全局变量声明集中管理:
// library.d.ts
declare let lodash: any;
declare let moment: any;
// 其他第三方库全局变量声明...
- 创建和维护自定义声明文件 。
//[模块名].d.ts
declare interface ViteEnv {
VITE_PORT: number;
VITE_PUBLIC: string;
VITE_GLO_TITLE: string;
VITE_DROP_CONSOLE: boolean;
VITE_COMPRESS: 'gzip' | 'brotli' | 'none';
...
}
declare namespace MyNamespace {
export function greet(name: string): void;
}
declare class MyClass {
constructor(value: number);
getValue(): number;
}
declare function myFunction(a: number): boolean;
声明文件的最佳实践 :
- 当我们开发自己的 JavaScript 库或者使用一些没有类型声明的现有库时,就需要创建自定义声明文件。创建声明文件的过程可以根据项目的实际情况而定。如果是对一个简单的 JavaScript 函数库进行类型声明,我们可以按照上述的模块声明方式逐个声明函数的类型。如果是一个大型的项目或库,可能需要更细致地规划声明文件的结构,例如按照功能模块或文件结构来组织声明内容。
- 在项目中引用声明文件也很简单。如果声明文件与对应的 JavaScript 代码在同一目录下,并且文件名相同(除了扩展名),TypeScript 编译器会自动识别并应用声明文件。如果声明文件在其他目录,我们可以通过
tsconfig.json
文件中的include
或files
选项来指定声明文件的路径,或者在使用的代码文件中使用三斜线指令/// <reference path="path/to/declaration.d.ts" />
来明确引用。 - 对于声明文件的维护,当库的代码发生变化,如添加了新的函数、修改了函数参数类型或返回值类型等,我们需要相应地更新声明文件。同时,在多人协作开发中,要确保声明文件的更新能够及时同步给所有使用该库的开发人员,以保证整个项目的类型一致性。
总之,这些声明文件可以很好的帮助 TypeScript 理解复杂的全局变量或模块 。
三、相关编译选项与配置
在 TypeScript 项目中,有一些与 declare
关键字相关的编译选项值得关注:
--allowJs
选项允许我们在 TypeScript 项目中包含 JavaScript 文件,这在处理既有 JavaScript 代码又需要添加类型声明的情况下非常有用。当我们启用这个选项时,就可以为 JavaScript 文件中的全局变量、函数等使用 Declare 关键字进行类型声明,逐步 将 JavaScript 代码纳入 TypeScript 的类型管理体系。
// tsconfig.json
{
"compilerOptions": {
"allowJs": true
}
}
- 另一个重要的选项是
--declaration
,当设置这个选项时,TypeScript 编译器会为我们的项目自动生成 相应的声明文件(.d.ts 文件)。这对于我们想要发布自己的 TypeScript 库并为使用者提供类型信息时非常关键。例如,我们开发了一个名为my-library
的库,在tsconfig.json
中设置了--declaration
选项后,编译项目时就会生成my-library.d.ts
文件,使用者在使用我们的库时就可以获得类型检查和代码提示的便利。
// tsconfig.json
{
"compilerOptions": {
"declaration": true
}
}
当然你也可以在命令行打开选项:$ tsc --allowJs
、$ tsc --declaration
。
四、declare
的常见误区
1. 误区:declare
会生成代码
实际上,declare
只影响类型检查,不会出现在编译后的JavaScript中 。以下代码:
declare var myVar: number;
console.log(myVar);
编译后将变成:
console.log(myVar); // Uncaught ReferenceError: myVar is not defined
你需要确保 myVar
在运行时存在,否则会抛出 ReferenceError
。
2. 误区:可以在 declare
中实现逻辑
declare
只能用于声明,不能包含逻辑实现。eg:
declare function add(a: number, b: number): number {
return a + b;
}
这段代码会报错❌,因为 declare
的用途是声明,而不是实现 。
关键点在于 declare
声明的内容不会出现在编译后的 JavaScript 中 ,因此编译器会禁止在 declare
中放入任何逻辑。正确的写法是声明和实现分离:
// 声明
declare function add(a: number, b: number): number;
// 实现
function add(a: number, b: number): number {
return a + b;
}
在这种写法中,declare
负责告诉 TypeScript 类型信息,而逻辑部分通过普通的函数实现。
为什么不能在 declare
中实现逻辑?
declare
是纯静态声明,不会影响运行时 \
它仅用于告诉 TypeScript 类型系统某些实体存在,不会产生实际的代码。- *编译后不保留声明* 如果你在
declare
中实现了逻辑,TypeScript 无法决定是否应该保留这些实现,因此直接禁止这种写法。
五、declare
的实际应用场景
1. 使用了没有类型定义的第三方库时
当我们项目中使用了一个 JavaScript 库没有提供 .d.ts
文件时,declare
可以临时为其定义类型:
declare module 'old-js-library' {
export function someMethod(): void;
}
2. 快速定义全局类型
在项目中临时声明某些全局变量或函数的类型:
declare const APP_VERSION: string;
console.log(`App version is ${APP_VERSION}`);
3. 为团队编写 .d.ts
类型声明
如果团队正在开发一个库,declare
是为团队成员使用提供类型定义的核心工具。
你还能想到其他的应用场景吗?欢迎在评论区中补充\~\~\~
六、总结
记住 declare
的核心作用 :只声明,不实现。它是静态类型检查的好帮手,但不能代替实际代码逻辑。
避免运行时错误 :使用 declare
声明的内容在运行时一定要存在,否则会抛出错误。
推荐工具 :在使用外部库时,优先查找官方或社区提供的类型定义文件,避免手动定义。
用好 declare
,你会发现 TypeScript 的开发体验大幅提升,从全局变量到外部库,再到模块类型检查,都能变得井井有条。希望本文能帮助你正确理解和使用 declare
关键字!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。