关于 TypeScript 中的 "*.d.ts" 文件

742
我对.d.ts声明文件很好奇,因为我对TypeScript编程语言还不熟悉。有人告诉我.d.ts文件类似于C和C++编程语言中的.h头文件,但是.d.ts文件似乎并不完全相同。目前,我还不明白如何正确使用.d.ts文件。看起来我不能将我的.js.ts文件添加到.d.ts文件中,所以我的项目只能包含这三种文件类型才能正常工作。这似乎是很多文件。为了帮助我更好地理解.d.ts文件与JavaScript和TypeScript的关系,我有一些问题想要问。
1. 三个文件之间有什么关系? 2. 我该如何使用 *.d.ts 文件?这意味着我可以永久删除 *.ts 文件吗? 3. 如果可以,*.d.ts 文件如何知道与之映射的 JS 文件是哪个?
如果有人能给我一个例子,那就太好了。

3
未来的读者请查看TypeScript文档:https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html。 - J.Ko
10个回答

862

“d.ts”文件用于提供使用JavaScript编写的API的TypeScript类型信息。您可能正在使用像jQuery或underscore这样的现有javascript库,希望从您的TypeScript代码中使用它们。

与其在TypeScript中重写jquery或underscore等内容,您可以编写仅包含类型注释的d.ts文件。然后,您可以在TypeScript代码中获得静态类型检查的TypeScript优势,同时仍然使用纯JS库。

这是因为TypeScript的约束不允许您在"import"语句的末尾添加".ts"扩展名。因此,当您引用某个文件(比如my-module.js)时,如果旁边有一个my-module.d.ts文件,那么TypeScript会将其内容包含进来:

src/
  my-module.js
  my-module.d.ts
  index.ts

my-module.js

const thing = 42;

module.exports = { thing };

my-module.d.ts

export declare const thing: number;

index.ts

->

index.ts

import { thing } from "./my-module"; // <- no extension

// runtime implementation of `thing` is taken from ".js"
console.log(thing); // 42

// type declaration of `thing` is taken from ".d.ts"
type TypeOfThing = typeof thing; // number

75
非常感谢!但如何将 *.d.ts 文件映射到一个 js 文件上呢?js 文件如何知道哪个 d.ts 文件与自己对应?你能给我举个例子吗? - user3221822
5
但是d.ts文件是由js文件生成的,如果js文件对d.ts一无所知,那么该如何在其他ts文件中调用d.ts中的函数呢?我感到困惑...... - user3221822
14
请参考 https://dev59.com/F2Ml5IYBdhLWcg3w1p26。您需要在使用 TypeScript 的文件的顶部添加一个 ///<reference 行。您需要同时拥有 d.ts 和 .js 文件。注意保持原文意思不变,并让翻译更加通俗易懂。 - Chris Tavares
3
d.ts文件通常是根据JS文件文档手写的。许多流行的JavaScript库都有这样的文件可用:https://github.com/borisyankov/DefinitelyTyped - basarat
27
如果你正在为自己的项目创建自定义的 ".d.ts" 文件,那么应该将它们放在哪里? - Jay
显示剩余7条评论

153

d 代表 声明文件

TypeScript 脚本编译时,有一个选项可以生成一个声明文件(扩展名为 .d.ts),它作为编译后 JavaScript 中组件的接口。在此过程中,编译器会剥离所有函数和方法体,仅保留导出类型的签名。生成的声明文件可用于描述从 TypeScript 消费第三方 JavaScript 库或模块时导出的虚拟 TypeScript 类型。

声明文件的概念类似于 C/C++ 中的头文件。

declare module arithmetics {
    add(left: number, right: number): number;
    subtract(left: number, right: number): number;
    multiply(left: number, right: number): number;
    divide(left: number, right: number): number;
}

类型声明文件可以手动编写,用于现有的JavaScript库,就像为jQuery和Node.js所做的那样。
流行JavaScript库的大量声明文件集合托管在GitHub上,包括DefinitelyTypedTypings Registry。提供了一个命令行实用程序typings来帮助从存储库中搜索和安装声明文件。

6
注意:自TypeScript 2.0起,typings命令行工具不再必要。更现代的方法是通过@types命名空间下的npm存储库使用类型包装器。更多细节请参见https://github.com/typings/typings/blob/master/README.md。 - Burt_Harris
1
我有一个问题。我需要为仅在我的项目内部使用的.tsx文件生成.d.ts文件吗?除了为第三方库提供额外信息之外,它们还添加了其他内容吗? - Jazi
@Jazi 理想情况下,不需要额外的文件,你的.tsx文件就足够了。即使你正在创建一个使用TS的库,你的index.ts文件将包含类型并在安装后在node_modules中提供类型检查的访问权限。 - era5tone

124

从不得不费力地使用.d.ts文件来映射JavaScript项目中,我学到了以下内容。

使用.d.ts文件来映射JavaScript需要将您的.d.ts文件命名为与.js文件相同的名称。每个.js文件需要保持在与具有相同名称的.d.ts文件相同的目录中。将需要.d.ts文件中类型的JS/TS代码指向.d.ts文件。

例如: test.jstest.d.ts位于testdir/文件夹中,然后您可以像这样在React组件中导入它:

import * as Test from "./testdir/test";

像这样将.d.ts文件导出为命名空间:

export as namespace Test;
    
export interface TestInterface1{}
export class TestClass1{}

71
没有人回答如何将d.ts文件连接到js文件的问题,因此我认为这里是正确的地方。 - ConstantinM
6
请注意,如果您正在为一些您没有创建的JS创建声明文件(例如来自npm的软件包),则“.d.ts”文件的名称也必须与要导入的软件包相同。 - derpedy-doo
2
如果模块像这样被导入(不要问我为什么):import x from "x/x.js",即在末尾有文件扩展名,该怎么办?尝试将d.ts文件命名为x.js.d.ts,但似乎并没有起作用。 - ՕլՁՅԿ
1
文件 *.d.ts 必须与 *.js 文件在“同一文件夹”下并且名称“相同”。我认为上面的答案是正确的。 - HungNM2

46

一个特定案例的工作示例

假设您有一个通过npm共享的my-module

您可以使用npm install my-module进行安装。

你这样使用它:

import * as lol from 'my-module';

const a = lol('abc', 'def');
该模块的逻辑全部在index.js文件中:
module.exports = function(firstString, secondString) {

  // your code

  return result
}

要添加类型,请创建一个名为index.d.ts的文件:

declare module 'my-module' {
  export default function anyName(arg1: string, arg2: string): MyResponse;
}

interface MyResponse {
  something: number;
  anything: number;
}

42

就像@takeshin所说,.d代表TypeScript的声明文件(.ts)。

在回答这篇文章之前,需要澄清一些要点:

  1. TypeScript是JavaScript的语法超集。
  2. TypeScript不能自主运行,它需要被转译成JavaScript (从TypeScript转换为JavaScript)。
  3. "类型定义"和"类型检查"是TypeScript相对于JavaScript提供的主要附加功能。(了解TypeScript和JavaScript的区别)

如果您在思考TypeScript只是语法超集,那么它有什么好处-https://basarat.gitbooks.io/typescript/docs/why-typescript.html#the-typescript-type-system

回答这篇文章 -

正如我们讨论的那样,TypeScript是JavaScript的超集,需要将其转译为JavaScript。因此,如果库或第三方代码是用TypeScript编写的,它最终会转换为JavaScript,可以被JavaScript项目使用,但反之则不成立。

例如 -

如果您安装JavaScript库-

npm install --save mylib

尝试将其在 TypeScript 代码中导入 -

import * from "mylib";

你会遇到错误。

"找不到模块 'mylib'。"

正如 @Chris 所提到的,很多库(例如 underscore 和 Jquery)已经用 JavaScript 编写了。为 TypeScript 项目重新编写这些库并不是一个好的选择,需要另外一种解决方案。

为了解决这个问题,你可以在 JavaScript 库中提供名为 *.d.ts 的类型声明文件,就像上面的 mylib.d.ts 一样。类型声明文件只提供了在相应的 JavaScript 文件中定义的函数和变量的类型声明。

现在当你尝试 -

import * from "mylib";

mylib.d.ts被导入,它充当JavaScript库代码和TypeScript项目之间的接口。


9
你应该将mylib.d.ts放在哪里?它可以放在代码库的任何地方吗?只能放在顶级目录吗?是否需要某个配置文件指向它? - mpoisot

17

本回答假设您有一些JavaScript代码,您不想将其转换为TypeScript,但是希望以最小的更改获得类型检查的好处。

.d.ts文件类似于C或C++头文件。它的目的是定义接口。以下是一个示例:

mashString.d.ts

/** Makes a string harder to read. */
declare function mashString(
    /** The string to obscure */
    str: string
):string;
export = mashString;

mashString.js

// @ts-check
/** @type {import("./mashString")} */
module.exports = (str) => [...str].reverse().join("");

主要.js

// @ts-check
const mashString = require("./mashString");
console.log(mashString("12345"));

这里的关系是:mashString.d.ts 定义了一个接口,mashString.js 实现了该接口,main.js 使用这个接口。

为了使类型检查正常工作,您需要在 .js 文件中添加 // @ts-check。但这只检查了 main.js 是否正确使用了接口。为了确保 mashString.js 正确实现它,我们在导出之前添加了 /** @type {import("./mashString")} */

您可以使用 tsc -allowJs main.js -d 创建初始的 .d.ts 文件,然后根据需要手动编辑它们以改进类型检查和文档。

在大多数情况下,实现和接口具有相同的名称,例如这里的 mashString。但是您也可以拥有替代的实现。例如,我们可以将 mashString.js 重命名为 reverse.js,并拥有另一种实现 encryptString.js


7

6

我想我可以在这里添砖加瓦。

// somefile.d.ts
export type SomeItem = {
  weight: number
}

export type ItemStorage = {
  name: string
  items: SomeItem[]
}

// somefile.js
// @ts-check
/** @typedef { import('./somefile.d.ts').SomeItem } SomeItem */
/** @typedef { import('./somefile.d.ts').ItemStorage } ItemStorage */

/**
 * @param { StorageItem } item
 */
function doSomething(item) {
  item. // intellisense
  // your code here
}

这样做的好处是可以逐渐将类型引入到现有的JavaScript项目中。

1

由于源代码是最终的真相来源。这里似乎是实现:

/*
 * Every module resolution kind can has its specific understanding how to load module from a specific path on disk
 * I.e. for path '/a/b/c':
 * - Node loader will first to try to check if '/a/b/c' points to a file with some supported extension and if this fails
 * it will try to load module from directory: directory '/a/b/c' should exist and it should have either 'package.json' with
 * 'typings' entry or file 'index' with some supported extension
 * - Classic loader will only try to interpret '/a/b/c' as file.
 */
type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined;

并且

/**
 * Kinds of file that we are currently looking for.
 */
const enum Extensions {
    TypeScript  = 1 << 0, // '.ts', '.tsx', '.mts', '.cts'
    JavaScript  = 1 << 1, // '.js', '.jsx', '.mjs', '.cjs'
    Declaration = 1 << 2, // '.d.ts', etc.
    Json        = 1 << 3, // '.json'

    ImplementationFiles = TypeScript | JavaScript,
}

完整文件在https://github.com/microsoft/TypeScript/blob/main/src/compiler/moduleNameResolver.ts


-3

例如,您遇到了使用npm中的'alertifyjs'模块的问题。

  1. 创建“anyNameYoulike.d.ts”文件(例如,在src文件夹中创建此文件)
  2. 在文件中 declare module 'alertifyjs'; 输入图像描述
  3. 在tsconfig.json中 在“compilerOptions”下 "typeRoots": ["node_modules/@types", "src/anyNameYoulike.d.ts"]

请将此内容重新发布为评论。 - Yserbius
4
@Yserbius 这个答案的长度怎么可能是一条注释? - dan-klasson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接