xx.d.ts文件有什么用,为什么不引入都能生效?

2,457 阅读4分钟

背景简介

大家好,我是石小石!

在前端项目开发中,我们常常会遇到这样一种情况:项目中引入了某个第三方库,但它没有提供内置的 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.jsoninclude 范围内

只要你的 .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)导致该文件不再是全局声明编译器不会自动把它合并为全局作用域
语法错误编译器会跳过整个文件