声明文件(.d.ts):让 TypeScript 认识你的 JS 库

1. 技术难点:为什么 TS 不认识第三方 JS 库

你在 TypeScript 项目里 import 一个用 JS 写的第三方库(如某个老 npm 包),TS 会报错:

arduino 复制代码
Could not find a declaration file for module 'some-lib'.

原因:TS 需要".d.ts 声明文件"来描述模块的"形状" (导出哪些函数、什么参数类型)。如果这个库没自带类型声明,TS 就"不认识"它,于是:

  • import 报错/变成 any
  • 用错参数、属性拼错,都没有任何类型提示和检查

核心难点 :如何正确地写 .d.ts,让 TS 认识:

  1. 无类型的 JS 库
  2. 引入的全局变量(CDN 的 $_ 等)
  3. 非 JS 资源(.css.png.vue.json
  4. 全局方法/环境(process.envimport.meta

2. 完整解法:声明文件的分层写法

2.1 三种声明文件的位置

TS 会按以下优先级找类型:

位置 说明
@types/xxx(@types 命名空间) 社区维护的公认库类型(@types/jquery
库自带 types/typings 字段指向的文件 库作者写的
项目内 declare module 'xxx' 你自己补的(shim)

2.2 给 JS 库"声明模块"(最常见)

没有类型时,先在项目里建一个 xxx.d.ts 手写声明:

ts 复制代码
// 方法一:极简(但不推荐,会让类型是 any)
declare module 'some-lib';

// 方法二:精确声明导出(推荐)
declare module 'some-lib' {
  export function formatDate(date: Date, format?: string): string;
  export const version: string;
  export interface Options { locale?: string; timezone?: string; }
  export function parse(input: string, options?: Options): Date;
}

// 方法三:默认导出
declare module 'my-lib' {
  declare function main(): void;
  export default main;
}

2.3 声明全局变量(CDN / 挂 window)

如果库通过 <script> 引入,挂在全局上(不是 import),声明全局:

ts 复制代码
// global.d.ts
declare var $: any;                 // 太宽, 不推荐
declare function $... : any;        // 简略

// 规范做法:声明 jQuery 类型
interface JQuery { ... }
declare const $: {
  (selector: string): JQuery;
  ajax(options: object): void;
};

给全局对象挂自定义属性 (如 window.analytics):

ts 复制代码
// declaration.d.ts
declare global {
  interface Window {
    analytics?: { track: (event: string) => void };
  }
}
export {};

2.4 声明非 JS 资源模块(CSS/图片/Vue)

webpack/Vite 引入 .css.png.vue 需要 shim,否则 import 报错:

ts 复制代码
// shims.d.ts
declare module '*.css';           // CSS 模块
declare module '*.png';           // 图片
declare module '*.svg';
declare module '*.vue' {
  import type { DefineComponent } from 'vue';
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

2.5 声明全局环境(process.env / import.meta)

Node 环境或需要 process.env.XXX 有类型:

ts 复制代码
// env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    API_BASE?: string;      // 声明你能访问的环境变量,别的访问报错
    SECRET_KEY?: string;
  }
}

3. 实战:从一个无类型 JS 库到完整声明

假设老库 legacy-util.js 提供了一个 debounce

js 复制代码
// legacy-util.js(无类型)
exports.debounce = function (fn, wait) { /* ... */ };
exports.stringify = function (obj) { return JSON.stringify(obj); };

给它写 legacy-util.d.ts

ts 复制代码
// legacy-util.d.ts
declare module 'legacy-util' {
  export function debounce<T extends (...args: any[]) => any>(
    fn: T,
    wait?: number
  ): (...args: Parameters<T>) => void;

  export function stringify(obj: unknown, indent?: number): string;
}

之后 import { debounce } from 'legacy-util' 就有完整的类型提示了。

declare module 时注意

  • 必须在模块文件 (有 import/export)里写,或用 export {} 隔离,否则会污染全局。
  • declare module 'x' 匹配的是"模块解析",declare global 匹配全局。

4. 要点总结

  • 声明文件 .d.ts:描述 JS 模块形状,让 TS 认识无类型的库/全局/资源。
  • 模块声明declare module 'xxx' { export ... },精确写导出函数/类型。
  • 全局声明 :CDN/挂 window 用 declare global + declare var/const
  • 资源 shim.css/.png/.vue 需要 declare module '*.xxx'
  • 类型共享:能发布自己写的 .d.ts 给团队,良好类型也是一等公民。
  • 优先用社区 @types/xxx,缺失才自己写 shim;生产库建议通过 types 字段随包发布类型。
  • 别用 declare module 'x';(无花括号)当甩锅------它让类型变 any,失去检查价值。

一句话:.d.ts 是 TS 和 JS 世界的"翻译官"------给无类型的库/全局/资源补上形状描述,TS 才能帮你检查、提示、约束。会写声明文件,就不怕"TS 项目遇到老 JS 库"了。

相关推荐
晓得迷路了1 小时前
栗子前端技术周刊第 145 期 - Remix 3 RC、htmx 4.0、Rslib 1.0...
前端·javascript·react.js
恋猫de小郭1 小时前
OpenAI :GPT-6 开始你需要给 Skill 和 AGENTS.md 做一次大扫除了
前端·人工智能·ai编程
IT_陈寒1 小时前
Redis莫名连接失败,查了三天居然是配置的锅
前端·人工智能·后端
晴天161 小时前
Esbuild:前端构建工具的“速度革命”
前端
雪芽蓝域zzs1 小时前
第三十五节:Axios 统一错误拦截、401 Token 过期处理
前端·javascript·vue.js
XIE3921 小时前
TipKit:开源富文本编辑器套件,一套逻辑,任意风格!
前端·笔记·开源
范什么特西1 小时前
一些常用名词
开发语言·前端·javascript
晴天162 小时前
前端打包工具全景解析
前端
晴天162 小时前
Vite vs Webpack 全方位对比
前端·webpack·node.js