背景简介
大家好,我是石小石!
在前端项目开发中,我们常常会遇到这样一种情况:项目中引入了某个第三方库,但它没有提供内置的 TypeScript 类型声明。此时,如果你在 TypeScript 项目中直接使用这个库,编译器就会报错。
也许你很聪明,知道在项目中的shims-vue.d.ts
中通过declare声明此模块,来解决报错。
但你是否想过这些问题:
- 为什么在
.shims-vue.d.ts
文件中写一个declare声明就能解决报错? - .d.ts文件明明没有显示引入,为什么就生效了?
- 任意xxx.d.ts文件声明为什么都能发挥作用吗?
其实,能够写一行 declare module 'xxx'
就让 TypeScript 编译器“闭嘴”,看似简单,背后却隐藏着 TypeScript 类型系统的一些核心机制。
.d.ts
文件的作用
.d.ts
文件是 TypeScript 世界中的“翻译器”,它不负责运行代码,而是负责描述代码的结构、类型、接口、模块,使得 TypeScript 编译器“明白”你在干什么,从而让类型检查、提示、自动补全等功能得以正常运作。
- 为 JavaScript 代码或第三方库补充类型
当你引入一个 没有类型定义 的库时,比如老旧的 JS 库或某些 npm 包没有内置 TypeScript 类型,会导致 TS 编译器报错。这时你可以写一个 .d.ts
文件,手动声明它的类型:
// types/jquery.d.ts
declare var $: any;
- 为非代码资源声明模块(如 SVG、CSS、JSON 等)
在项目中引入非代码资源时,TypeScript 默认是无法识别的,比如:
import logo from './logo.svg';
会报错:找不到模块。这时就需要一个 .d.ts
文件来告诉 TS 这是什么类型:
// types/shims-svg.d.ts
declare module '*.svg' {
const content: string;
export default content;
}
- 定义全局变量或类型
比如 VITE_APP_VERSION
是在构建时注入的变量,你可以这样声明:
// types/global.d.ts
declare const VITE_APP_VERSION: string;
这样就可以在任何文件中使用,不报错、还能获得类型提示。
- 定义环境变量、全局命名空间等复杂类型结构
例如:
interface Window {
myGlobalAPI: () => void;
}
declare namespace MyLib {
type Options = {
debug: boolean;
};
}
补充已有模块的类型信息(模块扩展)
你可以给已有模块添加自定义类型,不需要修改原始库代码:
// types/vue-router.d.ts
import 'vue-router';
declare module 'vue-router' {
interface RouteMeta {
auth?: boolean;
}
}
为什么写一个 declare
就能解决报错?
TypeScript 是强类型语言,它在编译时会尝试为每一个变量、函数、模块、类型标识符“找到定义”。当我们引入一个没有类型声明的第三方模块时,比如:
import something from 'vs-tree';
如果 vs-tree
没有提供 .d.ts
文件(也就是没有类型定义),TypeScript 编译器就会报错:
但当你写上:
// shims-vue.d.ts 或任何 .d.ts 文件
declare module 'vs-tree';
你就是手动告诉 TypeScript:“我知道这个模块存在,不用你担心类型问题。”于是 TS 编译器不再报错,默认把它当作 any
类型处理。这其实是使用 .d.ts
的一个最基础场景:模块声明补全。
为什么 .d.ts
文件不引入也能生效?
这要从 TypeScript 的文件识别机制说起。
TypeScript 在编译一个项目时,首先会加载项目根目录下的 tsconfig.json
,它会根据其中的配置项决定:
- 要包含哪些文件
- 要排除哪些文件
- 要使用哪些类型库(如 DOM、ESNext)
- 要如何解析模块路径(如路径别名)
具体来说,.d.ts
文件能自动生效,主要有以下几种情况:
在 tsconfig.json
的 include
范围内
只要你的 .d.ts
文件路径在 include
的匹配范围内,TS 编译器就会自动加载它:
{
"include": ["src", "types"]
}
如你把 shims-vue.d.ts
放在 src/
或 types/
下,它就会自动生效。
被编译器当作“全局声明”文件识别
.d.ts
文件中如果没有 import
/export
,就会被 TypeScript 当作“全局类型声明文件”(Global Declaration File),自动合并进全局作用域。这种文件中的内容对所有文件可见:
// types/global.d.ts
declare const __APP_VERSION__: string;
你在任意 .ts 文件中都能直接使用 __APP_VERSION__
,不需要任何引入。
被放置在默认类型目录下(如 @types
)
TypeScript 默认会去 node_modules/@types
中找类型定义(社区维护的 DefinitelyTyped 类型库)。如果你把声明文件放进这个路径,甚至可以模拟 npm 包类型的形式存在。
任意 xxx.d.ts
文件都能生效吗?
并不是,之前已经提到,只有在tsconfig.json中的 include
中被包含,编译器才会自动加载。
如上图,只有src目录下的任意xx.d.ts
才会被自动加载。但以下情况,即使在include中声明也不会被加载。
失败原因 | 解释 |
---|---|
使用了模块语法(如 import /export )导致该文件不再是全局声明 | 编译器不会自动把它合并为全局作用域 |
语法错误 | 编译器会跳过整个文件 |