欢迎光临
我们一直在努力

保姆级教程:手把手教你编写 TypeScript 声明文件

📝 引言

📌 场景/痛点

你在工作中一定遇到过这种情况:npm install 了一个好用的工具库,结果一引入,满屏飘红:Could not find a declaration file for module 'xxx'。

或者,项目里有一些遗留的 .js 文件,你不想重写它们,但希望在 TS 文件中引用它们时能有智能提示。

这时候,你需要手动编写 .d.ts 声明文件。 它是 TS 与 JS 世界之间的桥梁,也是前端工程师进阶的必修课。

✨ 最终效果

掌握本文后,你将能够为任何第三方库编写类型定义,甚至在完全没有源码的情况下,获得完美的代码补全。

在这里插入图片描述

📖 内容概览

本文将带你彻底搞懂声明文件:

  • 什么是 .d.ts:为什么它只有类型没有代码?
  • 核心语法:declare 关键字的使用。
  • 实战演练:为一个无类型的 JS 库编写声明。
  • 发布规范:如何在 package.json 中关联类型文件。

  • 🛠️ 正文

    1. 环境准备

    假设我们引入了一个名为 legacy-lib 的老旧 JS 库,它只导出了一个 calculate 函数和一个默认对象。

    2. 核心概念:什么是声明文件?

    .d.ts 文件(Definition File)是 TypeScript 的“契约”文件。

    • 它只包含类型定义(interface, type, declare)。
    • 不包含任何实现代码(不能写 function body)。
    • 编译器在编译时读取它,但在最终输出 js 时会直接忽略它。

    3. 核心关键字:declare

    declare 告诉 TS 编译器:“这个变量/函数/类虽然在当前文件里没有实现,但它肯定存在于运行时,你放心大胆地用吧。”

    3.1 常用声明语法

    // 声明一个函数
    declare function log(msg: string): void;

    // 声明一个变量/常量
    declare const version: string;

    // 声明一个类 (只写属性和方法签名,不写大括号内的逻辑)
    declare class Dog {
    name: string;
    constructor(name: string);
    bark(): void;
    }

    // 声明一个模块 (最常用)
    declare module "some-lib" {
    export function doSomething(): boolean;
    }

    4. 实战:为第三方库编写类型

    假设 legacy-lib 的源码是这样的 (JS):

    // node_modules/legacy-lib/index.js
    const calculate = (x, y) => x + y;
    const defaultOptions = { debug: true };
    module.exports = { calculate, defaultOptions };

    现在,我们要为它写一个声明文件。

    步骤一:创建类型文件

    在项目的 src/types/ 下新建 legacy-lib.d.ts。

    // src/types/legacy-lib.d.ts

    // 1. 使用 declare module 包裹,指定要扩展的包名
    declare module 'legacy-lib' {

    // 2. 定义函数签名
    export function calculate(x: number, y: number): number;

    // 3. 定义对象导出
    export const defaultOptions: {
    debug: boolean;
    };
    }

    步骤二:在代码中使用

    无需任何配置,TS 会自动识别项目中所有的 .d.ts 文件。

    // src/app.ts
    import { calculate, defaultOptions } from 'legacy-lib';

    // ✅ 有了类型!
    const res = calculate(10, 20);

    // ✅ 属性提示也出来了
    if (defaultOptions.debug) {
    console.log("Debug mode");
    }

    5. 进阶技巧:模块扩展与全局变量

    5.1 扩展 window 对象

    虽然之前讲过,这里再强调一下 .d.ts 写法。

    // src/types/global.d.ts

    // ⚠️ 注意:如果文件里没有任何 import/export,它会被视为全局脚本声明
    // 如果文件里有 import/export,则必须使用 declare global
    declare global {
    interface Window {
    myCustomApp: {
    init(): void;
    };
    }
    }

    // 确保 export {} 让文件成为一个模块,避免污染全局作用域或冲突
    export {};

    6. 发布规范:package.json 中的 types

    如果你是库的作者,想让用户下载你的包就能自动获得类型,请在 package.json 中配置:

    {
    "name": "awesome-lib",
    "main": "./dist/index.js",
    "types": "./dist/index.d.ts", // 🌟 关键字段!
    // 或者用 TypeScript 的旧称 "typings"
    "typings": "./dist/index.d.ts"
    }

    这样,当用户 import … from 'awesome-lib' 时,TS 会自动去寻找对应的 .d.ts 文件。


    ❓ 常见问题

    Q1: index.d.ts、@types/name 和 node_modules 是什么关系?

    A:

    • 自动安装:当你安装 lodash 时,如果它自带的 package.json 里有 types 字段,TS 会用那个。
    • @types:如果库本身没写类型,你可以安装 @types/lodash(这是社区贡献的)。
    • 自己写:如果社区也没有(或者定义错了),你就在自己项目的 src/types 里写 .d.ts 覆盖它。

    Q2: 我写了 .d.ts 文件,为什么还是提示找不到?

    A: 检查 tsconfig.json:

    • 确保 include 包含了你存放 .d.ts 的文件夹。
    • 如果是全局声明,确保 typeRoots 配置正确(默认是 node_modules/@types 和同级目录下的 **/*.d.ts)。

    Q3: declare module 'any-lib' 写完后,里面的类型能导入 TS 类吗?

    A: 可以!

    import { User } from './models'; // 导入项目内的类型

    declare module 'some-api' {
    // 在声明中使用外部类型
    export function getUser(): User;
    }

    这样你的类型就能闭环了。


    🎯 总结

    声明文件是 TypeScript 生态繁荣的基石。本文我们掌握了:

  • 核心概念:.d.ts 是只有类型的契约文件。
  • 语法:使用 declare 关键字定义变量、函数、模块和类。
  • 实战:为无类型的 JS 库补充类型定义。
  • 发布:通过 package.json 的 types 字段发布类型。
  • 🚀 下期预告:
    随着项目越来越大,类型检查越来越慢,每次保存都要等半天 tsc 编译。
    下一篇文章我们将深入 《类型性能优化:让你的大型项目飞起来》,掌握加速编译的核心技巧。

    💬 互动环节:
    你平时在项目中写过自定义的 .d.ts 吗?遇到过最难填的坑是什么?
    如果觉得文章帮你解决了类型报错,请点赞👍、收藏⭐、关注👀,下期更精彩!

    赞(0)
    未经允许不得转载:171主机测评 » 保姆级教程:手把手教你编写 TypeScript 声明文件
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址