JSDoc 与 .d.ts 自动生成
本教程共 80 篇 · 第 66 篇 · 更新于 2026-08-10 · 约 14 分钟阅读
本节目标:学会两种”不用手写 .d.ts”的类型安全方案——用 JSDoc 注解给 JS 代码加类型,以及通过
tsc --declaration自动从 TS 源码生成声明文件。你还将了解allowJs+checkJs混合项目的配置,以及从纯 JS 渐进迁移到 TypeScript 的路线。
问题:可以不写 .d.ts 吗
前五章我们都在手写声明文件——declare namespace、declare module、module augmentation。这些技能在你用第三方 JS 库时必不可少。
但换个角度想:如果你的项目本身就是 TypeScript 写的,或者你有一个不打算改成 .ts 的 JS 项目但想加点类型安全——声明文件还有必要手写吗?
答案是不一定。TypeScript 提供了两条自动化路径:
- JSDoc 注解:在
.js文件里通过注释标注类型,让 TS 编译器帮你检查。 tsc --declaration:从.ts源码自动生成.d.ts声明文件,零手工维护。
JSDoc 类型注解入门
JSDoc 本来是为 JS 代码生成文档用的注释规范,TypeScript 把它”升级”成了类型标注工具。在 .js 文件里写 JSDoc 注释,TS 编译器能读懂并做类型检查。
最常用的几个标签:
@type —— 给变量指定类型
/** @type {number} */
let count;
count = 42; // ✅
count = "hello"; // 如果开了 checkJs,这里会报错
@type 支持完整的 TypeScript 类型语法:
/** @type {string | number} */
let id;
/** @type {number[]} */
let scores;
/** @type {{ name: string; age: number }} */
let user;
/** @type {(a: number, b: number) => number} */
let add;
/** @type {Promise<string>} */
let promise;
@param 和 @returns —— 给函数标注类型
/**
* 计算两点之间的距离
* @param {number} x1 第一个点的 x 坐标
* @param {number} y1 第一个点的 y 坐标
* @param {number} x2 第二个点的 x 坐标
* @param {number} y2 第二个点的 y 坐标
* @returns {number} 两点间的欧氏距离
*/
function distance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
distance(0, 0, "3", 4); // checkJs 下会报错:第 3 个参数应为 number
@param 的语法是 @param {类型} 参数名 说明。类型可以是基础类型、联合类型、泛型,甚至复杂的 TS 类型语法。
@typedef —— 定义可复用的类型别名
/**
* @typedef {Object} User
* @property {number} id
* @property {string} name
* @property {string} [email] 方括号表示可选属性
* @property {"admin" | "user"} role
*/
/**
* @param {User} user
* @returns {string}
*/
function getUserDisplay(user) {
return `${user.name} (${user.role})`;
}
@typedef 定义的 User 类型可以在当前文件的其他 JSDoc 注释里复用。需要跨文件复用?用 @import:
/** @typedef {import("./types").User} User */
@callback —— 定义回调函数类型
/**
* @callback EventHandler
* @param {string} eventName
* @param {*} [data]
* @returns {void}
*/
/** @type {EventHandler} */
let handler = (eventName, data) => {
console.log(eventName, data);
};
@template —— 泛型
/**
* @template T
* @param {T} value
* @returns {T}
*/
function identity(value) {
return value;
}
identity("hello"); // 返回类型被推断为 string
identity(42); // 返回类型被推断为 number
checkJs 与 allowJs
光写 JSDoc 注解还不够——你需要让 TS 编译器真正检查 .js 文件。
单文件启用:在 .js 文件顶部加一行注释:
// @ts-check
/** @type {number} */
let x = "hello"; // 报错:类型 "string" 不能赋给 "number"
全局启用:在 tsconfig.json 里设置:
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"strict": false,
"noEmit": true
},
"include": ["src/**/*"]
}
两个配置项的区别:
allowJs: true:允许 TS 编译器处理.js文件(包括 import、类型推断)。checkJs: true:对.js文件进行类型检查(依赖allowJs为true)。
如果只开 allowJs 不开 checkJs,TS 会做类型推断但不报错——这意味着编辑器里你能看到类型提示和补全,但编译时不会因为类型错误而失败。这是一种非常低摩擦的过渡方案。
Tip从纯 JS 迁移到 TS 的推荐顺序:先开
allowJs(获取智能提示)→ 再开checkJs(开始出现类型警告)→ 逐步将文件改为.ts并开启strict。每一步都可以单独进行,不会阻塞业务开发。
混合项目配置
一个典型的渐进迁移项目,tsconfig.json 大概长这样:
{
"compilerOptions": {
"target": "ES2022",
"module": "esnext",
"moduleResolution": "bundler",
"allowJs": true,
"checkJs": true,
"strict": false,
"noEmit": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["src/legacy/**/*"]
}
几个值得注意的点:
strict: false:新迁移的 JS 项目一开始开strict可能报几百个错。先关掉,逐个文件迁成.ts后再开。noEmit: true:JS 文件不需要编译,只做类型检查。exclude:旧代码/第三方脚本可以暂时排除出去,不影响新代码的类型安全。
tsc —declaration:自动生成 .d.ts
如果你的项目本身就是 TypeScript 写的,.d.ts 声明文件完全不用手写——tsc 可以自动生成。
在 tsconfig.json 里加上两个选项:
{
"compilerOptions": {
"declaration": true,
"declarationDir": "./dist/types",
"emitDeclarationOnly": true
}
}
declaration: true:告诉 TS 为每个.ts文件生成对应的.d.ts。declarationDir:生成的.d.ts放到哪个目录。不设置的话就跟.js输出放在一起。emitDeclarationOnly: true:只生成.d.ts文件,不生成.js。适合你用 Babel 或 esbuild 编译 JS、只需要 TS 出类型声明的场景(比如库开发)。
给定一个源文件:
// src/utils.ts
export function add(a: number, b: number): number {
return a + b;
}
export interface Options {
timeout: number;
retries?: number;
}
export class TaskQueue {
private queue: (() => Promise<void>)[] = [];
enqueue(task: () => Promise<void>): void {
this.queue.push(task);
}
}
运行 tsc --declaration(或在 tsconfig.json 中配置后运行 tsc),生成的 utils.d.ts:
// dist/types/utils.d.ts
export declare function add(a: number, b: number): number;
export interface Options {
timeout: number;
retries?: number;
}
export declare class TaskQueue {
private queue;
enqueue(task: () => Promise<void>): void;
}
注意生成的文件自动加了 declare 修饰符,且函数实现体被移除了(只剩签名)。private 成员 queue 的类型标注也被省略——因为对于外部使用者来说,它是不可见的。
package.json 的 types 字段
作为 npm 包发布时,你需要告诉使用者声明文件在哪:
{
"name": "my-library",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"default": "./dist/index.js"
}
}
}
使用 exports 字段可以更细粒度地控制不同入口的类型文件。对于多入口的包尤其有用。
JSDoc vs .d.ts:什么时候用哪个
| 场景 | 推荐方案 |
|---|---|
| 你写 TS 源码,要发布 npm 包 | tsc --declaration 自动生成 |
| 你的项目是纯 JS,想渐进加类型 | JSDoc + allowJs + checkJs |
| 你在用第三方 JS 库,它没有类型 | 写 .d.ts(或找 @types) |
| 你要描述一个复杂的 API(重载、泛型等) | .d.ts(手写)比 JSDoc 更清晰 |
| 团队不打算用 TS,但想要类型提示 | JSDoc(零配置,编辑器就能读) |
JSDoc 的优缺点:
- 优点:不需要编译、不改变文件扩展名、团队零学习成本。
- 缺点:语法臃肿(一个简单类型要写三四行注释)、不支持所有 TS 类型特性(比如复杂的条件类型)、维护起来不如
.ts直观。
对于长期维护的项目,JSDoc 适合做过渡方案,最终目标还是迁移到 .ts。
总结
本章覆盖了两种自动化类型方案:
- JSDoc 注解:
@type、@param、@returns、@typedef、@callback、@template——给.js文件加类型标注,配合// @ts-check或checkJs: true启用检查。 tsc --declaration:从.ts源码自动生成.d.ts文件,适用于库开发和 TypeScript 项目。
声明文件系列到此结束。回顾六章的内容:从 .d.ts 的基本概念 → 全局库声明 → 模块库声明 → 插件与扩充 → @types 社区 → JSDoc 与自动生成。你已经掌握了”给 JS 代码加类型”的全套技能。
接下来,教程将进入 TypeScript 的工程化部分——tsconfig.json 编译选项的完整指南。