Skip to content

声明文件

.d.ts 文件是 TypeScript 的类型声明文件(Type Declaration File)

d.ts文件

d.ts是纯粹的类型声明文件,不产出任何 JS 代码

声明文件的作用:

  1. 提供类型给没有类型的 JS 文件

    比如你写的是 math.js,JS 本身没有类型,写一个 math.d.ts 可以给 TS 提示类型。

  2. 对外暴露库的类型声明

    如果你写的是一个 npm 包,编译后会产生一个d.ts文件和js文件。d.ts提供给开发者阅读。TS源码将不会泄露

  3. 模块增强或全局声明

    给现有模块加类型,或者声明全局函数/变量。

安装第三方类型文件

如果第三方库不存在类型声明文件,可以尝试使用 @types/模块名 安装

shell
pnpm i @types/lodash -D

ts文件类型

Script (脚本模式)

  • ts文件中没有 import 或 export 语句的文件,默认就是脚本模式。
  • 文件里的顶级变量、声明会被认为是 全局的,可能会和别的文件冲突。

假如你有两个文件:

ts
// a.ts
interface User {
  name: string;
}
ts
// b.ts
interface User {
  id: number;
}
ts
interface User {
  name: string;
  id: number;
}

因为它们都是 脚本模式,TypeScript 会自动把这两个 User 接口合并到全局里,导致奇怪的类型混淆。

Module(模块模式)

  • 只要文件中出现了 import 或 export,它就会被当成模块。
  • 模块里的顶级变量、函数、接口都只在这个模块作用域里生效,不会污染全局。
  • 其他文件没有import导入该文件,那么该文件就不会被TS编译。

Script -> Module

export {}; 就能强制把文件标记为模块

ts
// types/user.d.ts
export {};          // ← 文件变成模块
interface User {    // ← 既没 export,也不在全局
  name: string;
}

declare

declare 是一个关键字,用于声明某个标识符(变量、函数、类、模块等)的存在,但不给出具体实现。它告诉 TypeScript:“这个东西是存在的,但它在别的地方定义,你别报错。”。declare关键字可以在ts文件或者.d.ts中使用。

声明变量 / 常量

ts
declare const VERSION: string;
declare let count: number;
declare var window: Window;

声明函数

ts
declare function alert(message?: any): void;
declare function sum(a: number, b: number): number;

声明全局类型

ts
export {}
declare global {
  interface Window {
    myAppVersion: string;
  }
}

脚本模式下会忽略declare global{},因为脚本模式就已经是全局作用域了,TS编译器不会认识declare global{},因此需要将脚本模式切换成模块模式,全局类型声明才会生效。你也可以直接在脚本模式下声明全局类型:

ts
  interface Window {
    myAppVersion: string;
  }

声明模块

  • 声明全局式模块类型
ts
// types/a.d.ts   全局声明
declare module 'my-lib' {
  export function f1(): void;
}

只要 types/a.d.tstsconfig.include 里,任何地方 import { f1 } from 'my-lib' 都能用。

  • 声明可合并式模块类型

如果 没有任何 TS 文件import './types/b'import 'types/b',那么 f2 永远不存在,会报 TS2305: Module '"my-lib"' has no exported member 'f2'.

ts
// types/b.d.ts   模块级扩充
export {};                       // 或任何 import/export
declare module 'my-lib' {
  export function f2(): void;
}
ts
// components.d.ts
declare module 'vue' {
  export interface GlobalComponents {
    CaseButton: typeof import('./../components/CaseButton.vue')['default']
  }
}

// 🔑 关键:让本文件成为「模块」,避免污染全局
export {}

没有 export {} 时,TS 会把它当成“全局声明”,后续 runtime-dom 再想写 declare module 'vue' { ... } 就会冲突; 加了 export {},文件处于 模块作用域,同名模块声明就能被 TS 合并(augment)——这正是 Vue 想要的“插件式”类型扩充机制。

TypeScript 怎么理解模块?

当你写:

ts
import { doSomething } from 'my-module';

TypeScript 会去查:

  • 是否是ts文件
  • 是否存在declare module 'my-module'
  • 里面有没有 导出 doSomething 的类型信息

如果没有,TS 就会报错:“找不到模块 ‘my-module’ 的类型定义。”

declare module 里写的类型需要和模块中对应的成员名字相同,类型的导出方式要和模块中的导出方式相同。这样TS系统就能识别模块中成员的类型。

声明接口或类型

ts
// types.d.ts
declare interface Window {
  __INITIAL_DATA__: any;
}

示例

ts
// 导入express模块
import express from 'express';

// 创建一个express应用实例
const app = express();

// 设置一个路由,响应根路径的GET请求
app.get('/', (req, res) => {
  res.send('Hello, Express!');
});

// 监听端口3000,启动服务器
app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});
ts
declare module 'express' {
  // 定义 express 相关类型
  export type RequestHandler = (req: Request, res: Response, next: NextFunction) => void;
  
  export interface Request {
    body: any;
    params: { [key: string]: string };
    query: { [key: string]: string };
    // 其他常用属性可以继续扩展...
  }

  export interface Response {
    send: (body: any) => this;
    status: (code: number) => this;
    json: (data: any) => this;
    // 其他常用方法可以继续扩展...
  }

  export interface NextFunction {
    (): void;
  }

  export interface Express {
    get: (path: string, handler: RequestHandler) => void;
    listen: (port: number, callback: () => void) => void;
    use: (middleware: RequestHandler) => void;
  }

  export function express(): Express;
}