比較 JavaScript 與 .d.ts 示例
常見的 CommonJS 模式
使用 CommonJS 模式的模組使用 module.exports 來描述匯出的值。例如,這是一個匯出函式和數值常量的模組
jsconst maxInterval = 12;function getArrayLength(arr) {return arr.length;}module.exports = {getArrayLength,maxInterval,};
這可以透過以下 .d.ts 檔案來描述
tsexport function getArrayLength(arr: any[]): number;export const maxInterval: 12;
TypeScript 演練場(Playground)可以向您展示 JavaScript 程式碼對應的 .d.ts。您可以 點選此處親自嘗試。
.d.ts 語法特意看起來像 ES 模組語法。ES 模組於 2015 年作為 ES2015 (ES6) 的一部分由 TC39 批准,雖然它透過轉譯器已經存在很長時間了,但如果您有一個使用 ES 模組的 JavaScript 程式碼庫
jsexport function getArrayLength(arr) {return arr.length;}
它將具有以下 .d.ts 等價形式
tsexport function getArrayLength(arr: any[]): number;
預設匯出
在 CommonJS 中,您可以匯出任何值作為預設匯出,例如這是一個正則表示式模組
jsmodule.exports = /hello( world)?/;
它可以由以下 .d.ts 描述
tsdeclare const helloWorld: RegExp;export = helloWorld;
或者一個數字
jsmodule.exports = 3.142;
tsdeclare const pi: number;export = pi;
CommonJS 中匯出的一種風格是匯出函式。因為函式也是物件,所以可以新增額外的欄位並將其包含在匯出中。
jsfunction getArrayLength(arr) {return arr.length;}getArrayLength.maxInterval = 12;module.exports = getArrayLength;
這可以透過以下方式描述
tsdeclare function getArrayLength(arr: any[]): number;declare namespace getArrayLength {declare const maxInterval: 12;}export = getArrayLength;
處理多種匯入方式
在現代消費程式碼中,匯入模組的方式有很多種
tsconst 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 模組看起來應該像這樣
jsclass 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 程式碼提供型別
jsfunction getArrayMetadata(arr) {return {length: getArrayLength(arr),firstObject: arr[0],};}module.exports = {getArrayMetadata,};
這可以透過以下方式描述
tsexport type ArrayMetadata = {length: number;firstObject: any | undefined;};export function getArrayMetadata(arr: any[]): ArrayMetadata;
這個例子是 使用泛型 提供更豐富型別資訊的一個很好的案例
tsexport 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 上下文中將在全域性範圍內可用
tsexport 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
這些可以作為以下方式匯入
jsvar 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 module 的大括號內,並檢視您的使用在何處報錯