将 JavaScript 与示例 DTS 进行比较
常见的 CommonJS 模式
使用 CommonJS 模式的模块使用 module.exports
来描述导出的值。例如,以下是一个导出函数和数值常量的模块
js
const maxInterval = 12;function getArrayLength(arr) {return arr.length;}module.exports = {getArrayLength,maxInterval,};
这可以用以下 .d.ts
来描述
ts
export function getArrayLength(arr: any[]): number;export const maxInterval: 12;
TypeScript playground 可以显示 JavaScript 代码的 .d.ts
等效代码。你可以 在这里尝试一下。
.d.ts
语法有意看起来像 ES 模块 语法。ES 模块在 2015 年被 TC39 批准为 ES2015 (ES6) 的一部分,虽然它已经可以通过转译器使用很长时间了,但是如果你有一个使用 ES 模块的 JavaScript 代码库
js
export function getArrayLength(arr) {return arr.length;}
这将有以下 .d.ts
等效代码
ts
export function getArrayLength(arr: any[]): number;
默认导出
在 CommonJS 中,你可以将任何值导出为默认导出,例如,以下是一个正则表达式模块
js
module.exports = /hello( world)?/;
这可以用以下 .d.ts 来描述
ts
declare const helloWorld: RegExp;export default helloWorld;
或者一个数字
js
module.exports = 3.142;
ts
declare const pi: number;export default pi;
CommonJS 中的一种导出风格是导出一个函数。因为函数也是一个对象,所以可以添加额外的字段,这些字段也会包含在导出中。
js
function getArrayLength(arr) {return arr.length;}getArrayLength.maxInterval = 12;module.exports = getArrayLength;
可以用以下方式描述
ts
export default function getArrayLength(arr: any[]): number;export const maxInterval: 12;
请注意,在您的 .d.ts 文件中使用 export default
需要 esModuleInterop: true
才能正常工作。如果您无法在项目中使用 esModuleInterop: true
,例如在向 Definitely Typed 提交 PR 时,则必须使用 export=
语法。这种较旧的语法更难使用,但可以在任何地方使用。以下是使用 export=
编写上述示例的方式
ts
declare function getArrayLength(arr: any[]): number;declare namespace getArrayLength {declare const maxInterval: 12;}export = getArrayLength;
有关其工作原理的详细信息,请参阅 模块:函数,以及 模块参考 页面。
处理多个消费导入
在现代消费代码中,有很多方法可以导入模块
ts
const fastify = require("fastify");const { fastify } = require("fastify");import fastify = require("fastify");import * as Fastify from "fastify";import { fastify, FastifyInstance } from "fastify";import fastify from "fastify";import fastify, { FastifyInstance } from "fastify";
要涵盖所有这些情况,JavaScript 代码实际上需要支持所有这些模式。为了支持其中许多模式,CommonJS 模块需要类似于以下内容
js
class FastifyInstance {}function fastify() {return new FastifyInstance();}fastify.FastifyInstance = FastifyInstance;// Allows for { fastify }fastify.fastify = fastify;// Allows for strict ES Module supportfastify.default = fastify;// Sets the default exportmodule.exports = fastify;
模块中的类型
您可能希望为不存在的 JavaScript 代码提供类型
js
function getArrayMetadata(arr) {return {length: getArrayLength(arr),firstObject: arr[0],};}module.exports = {getArrayMetadata,};
可以用以下方式描述
ts
export type ArrayMetadata = {length: number;firstObject: any | undefined;};export function getArrayMetadata(arr: any[]): ArrayMetadata;
此示例非常适合 使用泛型 来提供更丰富的类型信息
ts
export type ArrayMetadata<ArrType> = {length: number;firstObject: ArrType | undefined;};export function getArrayMetadata<ArrType>(arr: ArrType[]): ArrayMetadata<ArrType>;
现在,数组的类型会传播到 ArrayMetadata
类型中。
导出的类型可以通过 TypeScript 代码中的 import
或 import type
,或 JSDoc 导入 被模块的使用者重复使用。
模块代码中的命名空间
尝试描述 JavaScript 代码的运行时关系可能很棘手。当 ES 模块式语法无法提供足够的工具来描述导出时,可以使用 `namespaces`。
例如,您可能拥有足够复杂的类型来描述,因此您选择在 `。d.ts` 中对它们进行命名空间。
ts
// This represents the JavaScript class which would be available at runtimeexport class API {constructor(baseURL: string);getInfo(opts: API.InfoRequest): API.InfoResponse;}// This namespace is merged with the API class and allows for consumers, and this file// to have types which are nested away in their own sections.declare namespace API {export interface InfoRequest {id: string;}export interface InfoResponse {width: number;height: number;}}
要了解命名空间在 `。d.ts` 文件中的工作原理,请阅读 `。d.ts` 深入探讨。
可选全局使用
您可以使用 `export as namespace` 来声明您的模块将在 UMD 上下文中全局范围内可用。
ts
export as namespace moduleName;
参考示例
为了让您了解所有这些部分如何组合在一起,以下是一个参考 `。d.ts`,可以作为创建新模块时的起点。
ts
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]// Project: [~THE PROJECT NAME~]// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>/*~ This is the module template file. You should rename it to index.d.ts*~ and place it in a folder with the same name as the module.*~ For example, if you were writing a file for "super-greeter", this*~ file should be 'super-greeter/index.d.ts'*//*~ If this module is a UMD module that exposes a global variable 'myLib' when*~ loaded outside a module loader environment, declare that global here.*~ Otherwise, delete this declaration.*/export as namespace myLib;/*~ If this module exports functions, declare them like so.*/export function myFunction(a: string): string;export function myOtherFunction(a: number): number;/*~ You can declare types that are available via importing the module */export interface SomeType {name: string;length: number;extras?: string[];}/*~ You can declare properties of the module using const, let, or var */export const myField: number;
库文件布局
您的声明文件布局应镜像库的布局。
库可以包含多个模块,例如
myLib +---- index.js +---- foo.js +---- bar +---- index.js +---- baz.js
这些可以导入为
js
var a = require("myLib");var b = require("myLib/foo");var c = require("myLib/bar");var d = require("myLib/bar/baz");
因此,您的声明文件应为
@types/myLib +---- index.d.ts +---- foo.d.ts +---- bar +---- index.d.ts +---- baz.d.ts
测试您的类型
如果您计划将这些更改提交到 DefinitelyTyped 供所有人使用,那么我们建议您
- 在
node_modules/@types/[libname]
中创建一个新文件夹- 在该文件夹中创建一个
index.d.ts
,并复制示例- 查看您的模块使用情况在哪里中断,并开始填写 index.d.ts
- 当您满意时,克隆 DefinitelyTyped/DefinitelyTyped 并按照 README 中的说明进行操作。
否则
- 在您的源代码树的根目录中创建一个新文件:
[libname].d.ts
- 添加
declare module "[libname]" { }
- 将模板添加到 declare 模块的大括号内,并查看您的使用情况在哪里中断