ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TypeScript 声明文件模板全指南:7 类库形态的 .d.ts 编写模板与实战用法

TypeScript 声明文件模板全指南:7 类库形态的 .d.ts 编写模板与实战用法 文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载导读在为一个 JavaScript 库编写 TypeScript 声明文件.d.ts时最快捷可靠的起点就是套用官方提供的声明文件模板。本指南以 TypeScript 中文手册中 templates.md 一节为核心系统讲解 7 个官方模板文件普通模块、函数模块、类模块、模块插件、全局库、全局插件、修改全局作用域的模块各自适用的库形态、识别方法、完整模板代码与关键编译选项。读完本文你将能够判断任意一个 JavaScript 库属于哪种形态并直接套用对应模板写出可发布、可消费、类型安全的声明文件。一、模板体系总览先识别库形态再选模板声明文件的组织方式取决于 JavaScript 库的使用方式。编写声明文件的第一步是识别库的类型——它如何被获取npm 还是 CDN、如何被导入全局对象、require还是import/export。库结构指南 将代码库分为以下几类每一类都有对应的官方模板文件库形态特征对应模板模块化代码库只能在使用模块加载器的环境下工作如 Node.jsmodule.d.ts、module-function.d.ts、module-class.d.ts、module-plugin.d.ts全局代码库不使用任何import语句即可通过全局作用域访问global.d.tsUMD 代码库既可作为 ES 模块导入也可在缺少模块加载器时作为全局变量使用视其可调用/可构造/普通属性分别套用三个模块模板全局插件一段全局代码改变某个全局变量如向Array.prototype添加方法global-plugin.d.ts修改全局作用域的模块导入时修改全局作用域中的值如向String.prototype添加成员global-modifying-module.d.ts其中模块化代码库的四个模板是使用最频繁的建议先阅读 module.d.ts 从整体上理解它们的工作方式再按下面的决策路径选用若模块可以当作函数调用const y x(42)使用 module-function.d.ts若模块可以使用new构造const y new x(hello)使用 module-class.d.ts若模块在导入后更改其它模块如require(jest-matchers-files)为 jest 添加匹配器使用 module-plugin.d.ts其余情况仅导出属性、类型、方法使用 module.d.ts。二、普通模块模板module.d.ts这是最基础的模块模板。适用于既不可调用、也不可构造只向外导出函数、类型、常量与嵌套命名空间的模块。完整模板见 module.d.ts核心结构如下// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~] // Project: [~THE PROJECT NAME~] // Definitions by: [~YOUR NAME~] [~A URL FOR YOU~] /*~ 如果该模块是 UMD 模块在没有模块加载器的环境下会暴露全局变量 myLib *~ 就在这里声明该全局变量否则删除此声明。 */ export as namespace myLib; /*~ 如果该模块有方法像这样把它们声明为函数。 */ export function myMethod(a: string): string; export function myOtherMethod(a: number): number; /*~ 通过导入模块即可使用的类型在这里声明。 */ export interface someType { name: string; length: number; extras?: string[]; } /*~ 模块的属性可以使用 const、let 或 var 声明。 */ export const myField: number; /*~ 如果模块内部还有带点号名称的类型、属性或方法 *~ 在 namespace 中声明它们。 */ export namespace subProp { /*~ 例如有了这个定义用户可以写 *~ import { subProp } from yourModule; *~ subProp.foo(); *~ 或 *~ import * as yourMod from yourModule; *~ yourMod.subProp.foo(); */ export function foo(): void; }使用该模板时需注意以下几点文件命名与放置模板文件应重命名为index.d.ts并放在与模块同名的文件夹中。例如为super-greeter编写声明时文件应为super-greeter/index.d.ts。export as namespace仅当模块同时以 UMD 方式暴露全局变量时才需要该声明如果库不提供全局访问方式应删除。它让模块在缺少模块加载器如直接通过script标签引入时也能以myLib全局名访问。可选属性extras?: string[]中的?表示该属性可省略属于可选属性。嵌套命名空间export namespace subProp用于描述模块中带点号的子属性访问如yourMod.subProp.foo()让模块的内部组织结构在类型层面同样可见。三、可调用函数模块模板module-function.d.ts许多代码库如 Express将自身导出为可调用函数。这类模块的典型用法是const x require(foo); const y x(42);。完整模板见 module-function.d.ts// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~] // Project: [~THE PROJECT NAME~] // Definitions by: [~YOUR NAME~] [~A URL FOR YOU~] /*~ 这是函数模块的模板文件。应重命名为 index.d.ts *~ 放在与模块同名的文件夹中如 super-greeter/index.d.ts。 */ // 注意ES6 模块不能直接导出类对象。 // 该文件应使用 CommonJS 风格导入 // import x require([~THE MODULE~]); // // 或者如果启用了 --allowSyntheticDefaultImports 或 // --esModuleInterop该文件也可以作为默认导入 // import x from [~THE MODULE~]; /*~ 如果该模块是 UMD 模块在没有模块加载器时暴露全局变量 myFuncLib *~ 在此声明否则删除。 */ export as namespace myFuncLib; /*~ 该声明指明函数就是本文件导出的对象。 */ export MyFunction; /*~ 下面的例子演示如何为函数声明多个重载。 */ declare function MyFunction(name: string): MyFunction.NamedReturnType; declare function MyFunction(length: number): MyFunction.LengthReturnType; /*~ 如果想同时导出模块内的类型把它们放在这个块中。 *~ 通常你会在这里描述函数返回值的形状。 *~ 注意如果包含该命名空间模块可能被错误地当作命名空间对象导入 *~ 除非开启 --esModuleInterop *~ import * as x from [~THE MODULE~]; // 错误不要这样做 */ declare namespace MyFunction { export interface LengthReturnType { width: number; height: number; } export interface NamedReturnType { firstName: string; lastName: string; } /*~ 如果模块还有属性也在这里声明。例如下面的声明使 *~ 以下代码合法 *~ import f require(myFuncLibrary); *~ console.log(f.defaultName); */ export const defaultName: string; export let defaultLength: number; }要点解析export 语法它表明导出对象就是该函数本身这是声明模块整体是一个函数的关键写法。函数重载模板展示了根据参数类型string/number返回不同类型NamedReturnType/LengthReturnType的重载写法同一函数可声明多个declare function。函数上的静态属性declare namespace MyFunction中的export const defaultName: string用于描述函数同时携带属性的情况这与 JavaScript 中f.defaultName的运行时行为一致。ES6 导入限制函数模块本质上是 CommonJS 风格的导出对象因此默认应使用import x require(...)若配置了allowSyntheticDefaultImports或esModuleInterop才可用默认导入import x from ...。这两个选项的详细说明可参考 tsconfig.json 参考。四、可构造类模块模板module-class.d.ts如果一个模块通过new关键字构造实例const x require(bar); const y new x(hello);则使用该类模板。完整模板见 module-class.d.ts// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~] // Project: [~THE PROJECT NAME~] // Definitions by: [~YOUR NAME~] [~A URL FOR YOU~] /*~ 这是类模块的模板文件。应重命名为 index.d.ts *~ 放在与模块同名的文件夹中如 super-greeter/index.d.ts。 */ // 注意ES6 模块不能直接导出类对象。 // 该文件应使用 CommonJS 风格导入 // import x require([~THE MODULE~]); // // 或者如果启用了 --allowSyntheticDefaultImports 或 // --esModuleInterop也可以作为默认导入 // import x from [~THE MODULE~]; /*~ 如果该模块是 UMD 模块在没有模块加载器时暴露全局变量 myClassLib *~ 在此声明否则删除。 */ export as namespace myClassLib; /*~ 该声明指明类的构造函数就是本文件导出的对象。 */ export MyClass; /*~ 在类中书写模块的方法和属性。 */ declare class MyClass { constructor(customGreeting?: string); greet: void; myMethod(opts: MyClass.MyClassMethodOptions): number; } /*~ 如果想同时导出模块内的类型可以放在这个块中。 *~ 注意如果包含该命名空间模块可能被错误地当作命名空间对象导入 *~ 除非开启 --esModuleInterop *~ import * as x from [~THE MODULE~]; // 错误不要这样做 */ declare namespace MyClass { export interface MyClassMethodOptions { width?: number; height?: number; } }要点解析export MyClassdeclare class用declare class声明类本身不包含实现再用export 将其作为模块导出对象。构造函数参数customGreeting?: string中的?表示可选参数模板对应的 JavaScript 场景为const Greeter require(super-greeter); const greeter new Greeter(); greeter.greet();。类静态侧与实例侧分离declare namespace MyClass与declare class MyClass同名合并后命名空间中的接口如MyClassMethodOptions可被类的方法签名引用对应类上挂载类型的常见库模式。关于 UMD 与模块导入模板注释明确指出该文件既可通过import x require(...)的 CommonJS 方式导入也可在开启esModuleInterop后用默认导入但不要使用import * as x from ...导入类模块除非开启esModuleInterop否则类模块会被错误地当作命名空间对象。五、模块插件模板module-plugin.d.ts模块插件会改变其它模块包括 UMD 或 ES 模块的结构。典型例子是 Moment.js 生态中的moment-range它为moment对象添加range方法。无论被增强的模块是 ES 模块还是 UMD 模块编写声明文件的方式相同。完整模板见 module-plugin.d.ts// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~] // Project: [~THE PROJECT NAME~] // Definitions by: [~YOUR NAME~] [~A URL FOR YOU~] /*~ 这是模块插件模板文件。应重命名为 index.d.ts *~ 放在与模块同名的文件夹中。 */ /*~ 在这一行导入该插件所增强的模块。 */ import * as m from someModule; /*~ 如果需要还可以导入其它模块。 */ import * as other from anotherModule; /*~ 在这里重新声明上面导入的同一个模块。 */ declare module someModule { /*~ 在内部添加新的函数、类或变量。可以使用 *~ 原模块中未导出的类型如果需要。 */ export function theNewMethod(x: m.foo): other.bar; /*~ 还可以通过接口增强interface augmentation *~ 为原模块的既有接口添加新属性。 */ export interface SomeModuleOptions { someModuleSetting?: string; } /*~ 也可以声明新类型它们看起来就像 *~ 定义在原模块中一样。 */ export interface MyModulePluginOptions { size: number; } }要点解析模块增强module augmentation通过import * as m from someModule导入目标模块再用declare module someModule重新声明该模块并追加导出这就是 TypeScript 的模块增强机制。注意外层文件本身必须是模块存在顶层import否则declare module无法生效。复用原模块类型theNewMethod(x: m.foo)表明增强后的函数可以引用原模块中的类型m.foo无需重新定义。接口增强export interface SomeModuleOptions为原模块的既有接口补充新属性让插件添加的配置项在类型层面生效。ES6 兼容性注意为已有模块的顶层导出添加或修改在 CommonJS 及其它模块加载器中是合法的但 ES6 模块是不可改变的因此该模式在 ES6 下不可行。TypeScript 是模块加载器无关的编译时不会限制该行为但开发者若要迁移到 ES6 模块加载器需留意这一点参见 library-structures.md 脚注。六、全局代码库模板global.d.ts全局代码库通过全局作用域访问不使用任何import语句。典型如 jQuery 的$变量$(() { console.log(hello!); });通常通过script src.../someLib.js/script标签引入。识别要点阅读全局库源码时你会看到顶层的var语句或function声明、一个或多个window.someName赋值、假设document或window存在而不会看到require/define调用、CommonJS/Node.js 风格的导入或描述require的文档。注意目前大多数流行的全局代码库实际上以 UMD 格式发布UMD 库与真正的全局库很难从文档区分。在编写全局声明文件前务必确认该库不是 UMD 库。完整模板见 global.d.ts它演示了可调用全局函数、同名接口、命名空间内属性/类/类型别名/函数的组合写法// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~] // Project: [~THE PROJECT NAME~] // Definitions by: [~YOUR NAME~] [~A URL FOR YOU~] /*~ 如果这个库可被调用例如 myLib(3) *~ 在此包含调用签名否则删除本节。 */ declare function myLib(a: string): string; declare function myLib(a: number): number; /*~ 如果希望库名本身能作为合法的类型名使用 *~ 可以在这里声明。 *~ 例如允许写 var x: myLib。 *~ 请务必确认这确实有意义如果没有意义 *~ 就删除此声明把类型放到下面的命名空间中。 */ interface myLib { name: string; length: number; extras?: string[]; } /*~ 如果库在全局变量上暴露了属性把它们放在这里。 *~ 类型interface 和 type alias也应该放在这里。 */ declare namespace myLib { //~ 可以写 myLib.timeout 50; let timeout: number; //~ 可以访问 myLib.version但不能修改它 const version: string; //~ 可以通过 let c new myLib.Cat(42) 创建实例 //~ 或引用 function f(c: myLib.Cat) { ... } class Cat { constructor(n: number); //~ 可以从 Cat 实例读取 c.age readonly age: number; //~ 可以调用实例方法 c.purr() purr(): void; } //~ 可以声明变量为 //~ var s: myLib.CatSettings { weight: 5, name: Maru }; interface CatSettings { weight: number; name: string; tailLength?: number; } //~ 可以写 const v: myLib.VetID 42; 或 const v: myLib.VetID bob; type VetID string | number; //~ 可以调用 myLib.checkCat(c) 或 myLib.checkCat(c, v); function checkCat(c: Cat, s?: VetID); }要点解析全局声明无需export全局声明文件中所有声明默认进入全局命名空间因此不要写顶层export除非需要配合模块系统。可调用与属性的分离declare function myLib描述可调用性declare namespace myLib描述挂载的属性、类与类型——函数与命名空间同名合并是声明可调用 带属性全局库的经典手法。只读属性const version: string与readonly age: number分别表达全局常量不可写和实例属性只读。类型别名type VetID string | number演示了用联合类型表达参数既可为字符串也可为数字。可选参数checkCat(c: Cat, s?: VetID)中的?表示第二个参数可省略。防止命名冲突的脚注虽然可以在全局作用域内定义许多类型但强烈建议不要这样做——当一个工程中存在多个声明文件时它可能导致难以解决的命名冲突。一个简单规则是使用代码库提供的某个全局变量来声明拥有命名空间的类型。例如如果代码库提供了全局变量cats应该写declare namespace cats { interface KittySettings {} }而不是在顶层写interface CatsKittySettings {}。这样做还能保证代码库日后可被转换成 UMD 模块且不影响声明文件的使用者详见 library-structures.md 脚注。七、全局插件模板global-plugin.d.ts全局插件是一段全局代码它会改变某个全局变量例如向Array.prototype或String.prototype添加新方法。这类库通常通过文档即可识别典型用法如下var x hello, world; // 在内置类型上创建新方法 console.log(x.startsWithHello()); var y [1, 2, 3]; // 在内置类型上创建新方法 console.log(y.reverseAndSort());完整模板见 global-plugin.d.ts// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~] // Project: [~THE PROJECT NAME~] // Definitions by: [~YOUR NAME~] [~A URL FOR YOU~] /*~ 这个模板演示如何编写全局插件。 */ /*~ 声明原始类型并为其添加新成员。 *~ 例如以下声明为内置的 number 类型添加带重载的 *~ toBinaryString 方法。 */ interface Number { toBinaryString(opts?: MyLibrary.BinaryFormatOptions): string; toBinaryString( callback: MyLibrary.BinaryFormatCallback, opts?: MyLibrary.BinaryFormatOptions ): string; } /*~ 如果需要声明若干类型请把它们放在命名空间中 *~ 避免向全局命名空间添加太多东西。 */ declare namespace MyLibrary { type BinaryFormatCallback (n: number) string; interface BinaryFormatOptions { prefix?: string; padding: number; } }要点解析接口增强全局类型直接对内置接口如Number重新声明并添加新方法TypeScript 会自动将新成员合并进该接口——这就是全局插件在类型层面的实现方式。重载设计示例给出了两种调用形态传opts对象或传回调函数回调中可选携带opts返回类型均为字符串。类型收纳进命名空间BinaryFormatCallback、BinaryFormatOptions等辅助类型放入declare namespace MyLibrary避免向全局命名空间塞入过多名称与防止命名冲突的脚注原则一致。八、修改全局作用域的模块模板global-modifying-module.d.ts某些模块在被导入时会修改全局作用域中的值——例如向String.prototype添加新成员。该模式存在运行时冲突的风险但 TypeScript 仍可为其编写声明文件。这类模块与全局插件相似但需要require语句来激活文档中常见如下形态// 不引用返回值的 require 调用 var unused require(magic-string-time); /* 或 */ require(magic-string-time); var x hello, world; // 在内置类型上创建新方法 console.log(x.startsWithHello()); var y [1, 2, 3]; // 在内置类型上创建新方法 console.log(y.reverseAndSort());完整模板见 global-modifying-module.d.ts// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~] // Project: [~THE PROJECT NAME~] // Definitions by: [~YOUR NAME~] [~A URL FOR YOU~] /*~ 这是修改全局作用域的模块模板文件。应重命名为 index.d.ts *~ 放在与模块同名的文件夹中。 */ /*~ 注意如果你的修改全局作用域的模块可被调用或可被构造 *~ 需要把这里的模式与 module-class 或 module-function *~ 模板文件中的模式结合起来。 */ declare global { /*~ 在这里声明进入全局命名空间的内容或增强 *~ 全局命名空间中已有的声明。 */ interface String { fancyFormat(opts: StringFormatOptions): string; } } /*~ 如果模块导出类型或值照常书写。 */ export interface StringFormatOptions { fancinessLevel: number; } /*~ 例如在模块上声明方法除了它的全局副作用之外。 */ export function doSomething(): void; /*~ 如果模块什么都不导出需要这一行。否则删除它。 */ export {};要点解析declare global块这是与全局插件模板的核心区别——本模板是模块含顶层import/export但通过declare global { ... }将声明注入全局命名空间从而增强String.prototype等全局类型。模块自身的导出照常书写export interface StringFormatOptions、export function doSomething()表明该模块在产生全局副作用的同时也可以正常导出类型与值。export {}的必要性若模块除了全局副作用外不导出任何内容必须写上export {}否则文件会被当作全局脚本而非模块declare global也就失去了意义。组合使用如果这类模块同时可被调用或构造需要将此模板与 module-class.d.ts 或 module-function.d.ts 的模式结合。九、声明文件中的依赖引用方式代码库可能有多种依赖模板的使用离不开正确的依赖声明方式。在 library-structures.md 的利用依赖一节 中给出了明确的规则你的库形态依赖对象写法任何形态全局库/// reference typessomeLib /任何形态模块import * as moment from moment;全局库UMD 模块/// reference typesmoment /ES 模块或 UMD 模块UMD 模块import * as someLib from someLib;// 依赖全局库使用三斜线指令 /// reference typessomeLib / function getThing(): someLib.thing;// 依赖模块使用 import 语句 import * as moment from moment; function getThing(): moment;其中有一条容易踩坑的规则不要使用/// reference ...指令来声明对 UMD 代码库的依赖——如果你的模块或 UMD 库依赖另一个 UMD 库应使用import语句否则模块加载器环境中的解析会出问题。十、模板的发布与版本管理写好声明文件后可以通过两种方式发布到 npm与源码捆绑发布或发布到types。若声明文件由源码生成可与源码一起发布--declaration编译选项参见 tsconfig.json 参考否则推荐提交到 DefinitelyTyped 发布为types包。与 npm 包捆绑时需要在package.json中通过types字段指向主声明文件{ name: awesome, author: Vandelay Industries, version: 1.0.0, main: ./lib/main.js, types: ./lib/main.d.ts }typings与types含义相同若主声明文件名为index.d.ts且位于包根目录与index.js并列则无需显式指定types字段。更详细的发布流程与typesVersions多版本选择机制可参考 publishing.md。十一、声明文件的目录结构组织模板文件的使用还依赖于正确的目录组织。声明文件的结构应该反映代码库源码的结构。例如一个包含多模块的库myLib ---- index.js ---- foo.js ---- bar ---- index.js ---- baz.js可通过require(myLib)、require(myLib/foo)、require(myLib/bar)、require(myLib/bar/baz)导入对应的声明文件布局为types/myLib ---- index.d.ts ---- foo.d.ts ---- bar ---- index.d.ts ---- baz.d.ts这正是前文反复强调将模板重命名为index.d.ts并放在与模块同名的文件夹中的原因——声明文件的路径必须与模块的导入路径一一对应TypeScript 才能正确解析。结语模板是起点识别是前提七个官方模板覆盖了 JavaScript 库的绝大多数组织形态普通模块、可调用函数模块、可构造类模块、模块插件、全局库、全局插件与修改全局作用域的模块。编写声明文件的完整工作流是先通过文档与源码判断库的形态全局 / 模块 / UMD / 插件再套用对应模板最后按 library-structures.md 的规则组织文件结构与依赖引用并参照 publishing.md 发布。所有模板的完整代码都可以在 zh/declaration-files/templates 目录下逐一查阅按需复制改造即可投入实战。赞分享文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载相关推荐企业级客服机器人语义理解实践指南如何利用GTE-large-zh提升智能客服效果企业级客服机器人语义理解实践指南如何利用GTE large zh提升智能客服效果 在当今数字化时代 GTE large zh 作为阿里巴巴达摩院开发的高性能文档教程Satellite Eyes用户指南从基础设置到高级自定义的完整教程Satellite Eyes用户指南从基础设置到高级自定义的完整教程 Satellite Eyes是一款专为Mac OS X打造的实用工具能够自动将您的桌面QtScrcpy3分钟实现安卓设备跨平台高清投屏控制QtScrcpy3分钟实现安卓设备跨平台高清投屏控制 QtScrcpy是一款基于Qt框架开发的Android实时显示控制软件通过USB或网络连接无需roo文档教程上一篇5分钟掌握对象存储元数据管理s3fs-fuse标签功能实战指南下一篇革命性3D点云配准DUSt3R全局优化算法深度解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表