引言:为什么你的代码能与 lodash 和谐共处?
你是否曾好奇过,TypeScript 是如何知道像 lodash 或 jQuery 这样的普通 JavaScript 库的类型的?你只是写了 import _ from 'lodash',然后突然就有了自动补全和类型检查。是魔法吗?不,这是神秘的 *.d.ts 文件在起作用。
在 Stack Overflow 上,“About "*.d.ts" in TypeScript”这个问题获得了数千次浏览——开发者们想了解这个扩展名背后隐藏着什么。今天,我们将深入 TypeScript 内部,剖析类型声明的工作原理。你不仅会学到理论知识,还会获得实用的示例,帮助你为任何 JavaScript 项目创建自己的类型。
.d.ts 到底是什么?
扩展名为 .d.ts 的文件是 TypeScript 声明文件。它们只包含类型描述,而不包含实现。可以把它们想象成一份“契约”:TypeScript 查看这个文件,就能理解存在哪些函数、类和变量,以及它们的类型是什么。
与普通 .ts 文件的关键区别在于:.d.ts 不会编译成 JavaScript。它们仅用于静态分析和自动补全。
// myLibrary.d.tsexport function greet(name: string): void;export const VERSION: string;现在,如果你导入这个库,TypeScript 就会知道:greet 接受一个字符串且不返回任何值,而 VERSION 是一个字符串。如果没有这个文件,你会收到错误信息“Could not find a declaration file for module”。
TypeScript 如何找到 .d.ts?
声明文件的查找机制分几个步骤进行:
- 自动查找:如果
.js文件旁边有一个同名的.d.ts文件,TypeScript 会自动加载它。 - package.json 中的 types 或 typings 字段:许多库会指定其声明文件的路径。例如:
"types": "./lib/index.d.ts"。 - /// <reference path="" /> 指令:手动指定声明文件(一种较旧但仍然存在的方法)。
- @types 包:如果库本身不包含类型,社区会将其发布到 npm,名称为
@types/库名。
当你运行 npm install @types/react 时,你下载的正是描述 React 的一组 .d.ts 文件。
内部原理:TypeScript 如何处理 .d.ts
让我们想象一下,TypeScript 是一名侦探,正在调查一桩“类型案件”。.d.ts 文件就是证人的证词。它们说:“这里有一个 add 函数,它接受两个数字并返回一个数字”。TypeScript 相信这些证词,并且不会去验证它们是否属实(因为没有实现)。
Ambient 声明
.d.ts 最强大的功能之一就是声明 ambient 模块。这允许你为代码中不存在的模块描述类型。例如,你想使用 jQuery 中的全局变量 $:
// globals.d.tsdeclare var $: (selector: string) => any;或者描述整个模块:
// my-custom-module.d.tsdeclare module 'my-custom-module' { export function doSomething(x: number): string; export interface Config { timeout: number; }}现在,你可以编写 import { doSomething } from 'my-custom-module'——TypeScript 就会知道这些类型。
全局类型 vs 模块类型
一个重要的区别:.d.ts 文件可以是全局的(如果其中没有 import/export),也可以是模块化的(如果有)。全局声明可见