作为 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 文件中的 includefiles 选项来指定声明文件的路径,或者在使用的代码文件中使用三斜线指令 /// <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 关键字!


十六
1 声望0 粉丝

前端学者~