面向JavaScript编程的你是否已经听说过TypeScript的".d.ts"文件,或者对它有一些困惑?在这篇文章中,我们将全面深入地探讨".d.ts"文件,并试图回答以下问题
-
.ts
、.js
和.d.ts
这三种文件之间的关系是什么? - 我们如何使用
.d.ts
文件?是否意味着我可以永久删除.ts
文件? - 如果可以,
.d.ts
文件如何知道哪个JS文件映射到自己?
".d.ts"文件是什么?
在TypeScript中,".d.ts"文件被用来提供关于JavaScript API的类型信息。当你使用像jQuery或underscore这样的现有JavaScript库时,你可能会希望从你的TypeScript代码中调用这些库。为了避免重写整个库,你可以编写一个".d.ts"文件,其中只包含类型注解。然后,你可以在TypeScript代码中享受静态类型检查的好处,同时仍然使用纯JavaScript库。
例如,考虑以下目录结构:
src/
my-module.js
my-module.d.ts
index.ts
在这个例子中,my-module.js
文件定义了一个常量:
const thing = 42;
module.exports = { thing };
然后,我们有一个与之配套的my-module.d.ts
文件,它声明了thing
的类型:
export declare const thing: number;
最后,在index.ts
文件中,我们导入并使用thing
:
import { thing } from "./my-module"; // <- 没有扩展名
// `thing`的运行时实现取自".js"文件
console.log(thing); // 42
// `thing`的类型声明取自".d.ts"文件
type TypeOfThing = typeof thing; // number
我们可以看到.d.ts
文件如何允许我们在不改变原有JavaScript代码的前提下,使用TypeScript的类型系统。
".d.ts"文件的起源和作用
".d.ts"文件是“声明文件”,在TypeScript编译过程中,有一个选项可以生成它。编译器会剥离所有函数和方法体,只保留导出类型的签名。生成的声明文件可以用来描述当第三方开发者从TypeScript中使用JavaScript库或模块时,导出的虚拟TypeScript类型。
声明文件的概念类似于C/C++中的头文件概念。它们可以为现有的JavaScript库手动编写,例如jQuery和Node.js,也有大量的声明文件集合托管在GitHub的DefinitelyTyped中,供流行的JavaScript库使用。
声明文件的示例:
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;
}
在这个例子中,我们定义了一个名为"arithmetics"的模块,它包含四个函数,每个函数接收两个数字作为参数,并返回一个数字。这个声明文件提供了使用这个模块的所有必要类型信息,但并没有包含任何实现细节。
".d.ts"文件与".ts"和".js"文件的关系
".d.ts"文件与".ts"和".js"文件的关系可以从两个角度来理解:文件关系和使用关系。
文件关系
如前所述,".d.ts"文件是TypeScript编译过程中生成的一个选项,它剥离了所有的函数和方法体,仅保留了导出类型的签名。因此,可以看作是".ts"文件的一个“裁剪”版本,只包含类型信息,不包含实现代码。另一方面,".js"文件是".ts"文件经过编译后生成的JavaScript代码,它包含实现细节,但没有类型信息。".d.ts"文件补充了这一点,为".js"文件提供了丢失的类型信息。
使用关系
在实际使用中,".d.ts"文件主要用于提供在JavaScript环境中使用TypeScript的类型信息,使得开发者可以在TypeScript中使用现有的JavaScript库,同时享受到TypeScript的静态类型检查的优势。".d.ts"文件并不能替代".ts"或".js"文件,它们之间是互补的关系,而不是取代的关系。".ts"文件提供了TypeScript代码的实现,".js"文件提供了JavaScript环境的运行代码,而".d.ts"文件则提供了类型信息。
因此,".d.ts"文件并不能知道哪个JS文件映射到它自己。当你在TypeScript中引用一个文件时,如果文件旁边有一个对应的".d.ts"文件,TypeScript会自动包含它的内容。这是因为TypeScript不允许你在import
语句的末尾添加".ts"扩展名。如果你引用的文件有一个同名的".d.ts"文件,TypeScript就会使用它的类型信息。
如何使用".d.ts"文件
在使用".d.ts"文件来映射JavaScript项目时,你需要将".d.ts"文件的名称与".js"文件的名称保持一致。每个".js"文件都需要与具有相同名称的".d.ts"文件保持内联(即在同一目录中)。需要从".d.ts"文件中获取类型的JS/TS代码应该指向".d.ts"文件。
例如,如果你有一个名为"test.js"的文件和一个名为"test.d.ts"的文件,它们都在"testdir/"文件夹中,那么你可以在react组件中这样导入它们:
import * as Test from "./testdir/test";
此外,".d.ts"文件应该像下面这样作为一个命名空间进行导出:
export as namespace Test;
export interface TestInterface1{}
export class TestClass1{}
对于特定的情况,例如你通过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;
}
我们定义了一个名为"my-module"的模块,它有一个默认的导出函数,这个函数接收两个字符串作为参数,并返回一个"myResponse"接口的实例。另外,我们定义了这个接口,它有两个数字类型的属性。
".d.ts"文件的最佳实践
- 与JavaScript文件保持一致:".d.ts"文件应与相应的JavaScript文件具有相同的名称,并与之保持内联。
- 为导出的类型提供定义:".d.ts"文件允许你为JavaScript库中的各种导出类型(包括函数、对象、类等)提供明确的类型定义。这对于提高代码的可读性和可维护性是非常有用的。
- 尽量使用模块化的方式组织代码:".d.ts"文件支持模块化的代码组织方式。你可以创建多个小的、具有明确职责的".d.ts"文件,而不是一个大的包含所有类型定义的文件。这有助于提高代码的可维护性。
- 为第三方库创建类型声明文件:如果你正在使用的第三方JavaScript库没有提供类型声明文件,那么你可以自己创建一个。这样,你就可以在使用这个库的同时,享受到TypeScript的类型检查功能。
- 使用DefinitelyTyped:DefinitelyTyped是一个大型的类型声明文件集合,包含了许多流行的JavaScript库的类型声明文件。如果你正在使用的库的类型声明文件已经存在于DefinitelyTyped中,那么你可以直接使用它,而不需要自己从头开始创建。