ARTICLE DETAIL

资讯详情

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

TypeScript 声明文件模板实战:为修改全局作用域的模块编写 global-modifying-module.d.ts

TypeScript 声明文件模板实战:为修改全局作用域的模块编写 global-modifying-module.d.ts 文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载导读本篇文章聚焦 TypeScript 声明文件.d.ts七种官方模板中最特殊的一种——global-modifying-module.d.ts全局修改模块模板。这类代码库在导入时并不会导出你需要的对象而是悄悄改写全局作用域例如向String.prototype、Array.prototype挂载新方法其声明文件写法与普通模块、UMD 模块截然不同。读完本文你将掌握如何从文档与源码中识别修改全局作用域的模块、如何用declare global块安全地扩充内置类型、如何用export {}把文件声明为模块以及如何规避运行时命名冲突风险。该模板位于 global-modifying-module.d.ts.md是 TypeScript 使用手册中文版 中声明文件 → 模板章节的核心内容之一与 global.d.ts、global-plugin.d.ts 同属全局系模板可互相参照。一、什么是修改了全局作用域的模块在 TypeScript 声明文件的世界里代码库大致分为模块化代码库、全局代码库Global、UMD 代码库、全局插件Global Plugin与全局修改模块Global Modifying Module等几类详见 library-structures.md。修改全局作用域的模块global-modifying module是一种很特殊的存在当你导入它时它不会或不仅仅向调用方返回某个对象而是直接改写全局作用域中的值。典型例子某个库在导入后向String.prototype上添加新成员。这意味着一旦有人require了它所有字符串实例都会凭空多出方法。模板文档原文如此描述对于修改了全局作用域的模块来讲在导入它们时会对全局作用域中的值进行修改。比如存在某个代码库当导入它时它会向String.prototype上添加新的成员。该模式存在危险因为它有导致运行时冲突的可能性但我们仍然可以为其编写声明文件。关键点在于这种模式存在运行时冲突风险多个库可能都想往同一个原型上挂同名方法或与未来 JavaScript 标准新增的原生方法撞名但既然社区中确实存在这类库TypeScript 依然提供了为它们编写类型声明的手段。与全局插件的区别需要把全局修改模块与全局插件区分开全局插件global plugin一段全局代码直接改变某个全局变量的结构典型如向Array.prototype、String.prototype增加新函数无需require即可生效模板见 global-plugin.d.ts全局修改模块global-modifying module与全局插件行为相似但**必须通过require/import语句来激活**其全局副作用。从模板原文看二者的识别区别就一句话通常来讲它们与全局插件类似但是需要require语句来激活。二、如何识别修改全局作用域的模块识别方法是先看文档。这类库的使用文档往往长这样来自模板原文// require call that doesnt use its return value var unused require(magic-string-time); /* or */ require(magic-string-time); var x hello, world; // Creates new methods on built-in types console.log(x.startsWithHello()); var y [1, 2, 3]; // Creates new methods on built-in types console.log(y.reverseAndSort());你可以根据以下特征做出判断require的返回值未被使用var unused require(...)甚至裸require(...)说明调用者根本不在乎模块导出了什么导入只是为了触发副作用导入后内置类型出现新方法文档中的示例立刻调用x.startsWithHello()、y.reverseAndSort()这类原本不存在的字符串/数组方法示例中没有任何解构导出没有const { xxx } require(...)之类的用法。反之如果一个库的文档要求const moment require(moment)并使用其返回值那它是普通模块或 UMD 模块不该套用本模板。从源码角度交叉验证在动手写声明文件前可进一步用源码佐证这也是声明文件编写指南推荐的做法。全局修改模块的源码通常会出现顶层String.prototype.xxx ...、Array.prototype.xxx ...之类的原型挂载代码顶层对global/window/globalThis的赋值同时具备module.exports或export语句所以它是一个模块与全局插件相区别。注意这类库大多还是 CommonJS / UMD 形态因此在声明文件里需要同时照顾模块导出与全局扩充两个层面这正是下面模板的设计逻辑。三、模板逐段精读global-modifying-module.d.ts模板原文完整如下来自 global-modifying-module.d.ts.md// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~] // Project: [~THE PROJECT NAME~] // Definitions by: [~YOUR NAME~] [~A URL FOR YOU~] /*~ This is the global-modifying 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 */ /*~ Note: If your global-modifying module is callable or constructable, youll *~ need to combine the patterns here with those in the module-class or module-function *~ template files */ declare global { /*~ Here, declare things that go in the global namespace, or augment *~ existing declarations in the global namespace */ interface String { fancyFormat(opts: StringFormatOptions): string; } } /*~ If your module exports types or values, write them as usual */ export interface StringFormatOptions { fancinessLevel: number; } /*~ For example, declaring a method on the module (in addition to its global side effects) */ export function doSomething(): void; /*~ If your module exports nothing, youll need this line. Otherwise, delete it */ export {};下面逐段拆解说明每一部分的职责与可替换点。3.1 头部注释文件命名与放置规范/*~ This is the global-modifying module template file. You should rename it to index.d.ts *~ and place it in a folder with the same name as the module. */这是所有模板共有的规范把模板重命名为index.d.ts并放入与模块同名的目录。例如为super-greeter写声明文件就放在super-greeter/index.d.ts。这与 library-structures.md 中代码库文件结构应反映源码结构的原则一致——源码是myLib/index.js、myLib/foo.js、myLib/bar/baz.js声明文件就对应types/myLib/index.d.ts、foo.d.ts、bar/index.d.ts、bar/baz.d.ts。文件头部的// Type definitions for ...与// Project: ...、// Definitions by: ...是 DefinitelyTyped 风格的标准署名注释如实填写库名、版本、项目地址与作者信息即可。3.2declare global块扩充全局命名空间declare global { interface String { fancyFormat(opts: StringFormatOptions): string; } }declare global是本模板的灵魂。它的作用是在一个模块.d.ts 文件只要含export就变成模块内部声明对全局命名空间的扩充——这里是给内置的String接口添加一个fancyFormat方法。这样整个工程里所有string类型的变量都能在类型层面调用fancyFormat与库在运行时的原型注入行为保持一致。使用declare global时注意它只能出现在外部模块即带export/import的文件中这是它区别于全局插件模板 global-plugin.d.ts直接顶层interface Number {...}的根本原因块内既可以扩充已有全局声明如给内置String加成员也可以声明全新的全局命名空间成员模板注释明确提醒Here, declare things that go in the global namespace, or augment existing declarations in the global namespace——也就是说这个块既负责新增也负责增强。3.3 普通模块导出类型与函数照常书写export interface StringFormatOptions { fancinessLevel: number; } export function doSomething(): void;全局修改模块毕竟是模块除了全局副作用它通常还导出一些类型和值。这部分按普通模块模板的写法处理即可export interface/export type导出与全局新增方法配套的选项类型如StringFormatOptionsexport function/export const导出模块自身的方法与属性。这样做的价值在于使用方既可以享受全局方法带来的类型提示又能在需要时显式导入模块提供的工具函数与配置类型。3.4export {}把文件标记为模块/*~ If your module exports nothing, youll need this line. Otherwise, delete it */ export {};这是一个容易忽略但极其关键的细节。只有当你的模块真的什么都不导出时才需要这行。它的作用是把整个.d.ts文件从全局脚本提升为外部模块——因为在模块语境下declare global才合法。如果你的模块已经存在其他export如上面的interface或function文件天然就是模块此时应删掉这行。用一句话总结模板的架构declare global描述全局副作用普通export描述模块导出二者缺一不可且必须共存于同一个模块化文件中。四、组合场景可调用 / 可构造的全局修改模块模板注释特别指出一个组合需求If your global-modifying module is callable or constructable, youll need to combine the patterns here with those in the module-class or module-function template files.也就是说如果这个库既修改全局作用域自身又能被当作函数调用const x require(foo); x(42)或能被new构造const y new x(hello)那么单一模板不够用需要组合可调用场景参考 module-function.d.ts用export MyFunctiondeclare function MyFunction(...)描述可调用导出用declare namespace收纳返回类型可构造场景参考 module-class.d.ts用export MyClassdeclare class MyClass {...}描述类导出全局副作用部分保留本模板的declare global块。组合时注意export 语法面向 CommonJS 风格import x require(...)使用若需配合 ES 模块默认导入可结合--allowSyntheticDefaultImports或--esModuleInterop编译选项详见 module-function.d.ts 头部注释。五、配套知识依赖声明与命名冲突防范5.1 声明文件中的依赖写法如果该库在全局副作用之外还依赖其他库依赖类型按 library-structures.md 中的规则声明依赖全局代码库用三斜线指令/// reference typessomeLib /依赖普通模块用import语句例如import * as moment from moment;全局代码库依赖 UMD 模块用/// reference typesmoment /模块 / UMD 库依赖 UMD 库用import * as someLib from someLib;不要用/// reference指令。5.2 防止命名冲突优先使用命名空间原文档与 library-structures.md 都强调不要在全局作用域顶层随意定义类型多份声明文件同时存在时极易产生难以排查的命名冲突。推荐做法是用库提供的全局变量作为命名空间容器。例如库提供全局变量catsdeclare namespace cats { interface KittySettings {} }而不是// at top-level interface CatsKittySettings {}这样写还有一个额外好处将来该库若改造成 UMD 模块现有声明文件的使用者不会受影响。对全局修改模块而言这条建议尤其重要——它本来就在往全局命名空间里塞东西新增类型若再堆在全局顶层冲突概率会成倍上升。因此模板中StringFormatOptions这类配套类型应当作为普通导出或放进命名空间而不是直接铺在全局。5.3 运行时的真实风险提示模板开篇就提醒了风险原型注入式的全局修改可能引发运行时冲突。编写和使用此类声明文件时应保持清醒两个库都往String.prototype添加同名方法时后者会覆盖前者与将来 ECMAScript 标准新增的原生方法重名时库可能被原生实现顶掉或反向破坏新特性在 ES6 模块加载器环境下模块顶层导出是不可变的向已有模块顶层添加/修改导出的插件模式在编译期不受 TypeScript 限制TypeScript 与模块加载器无关但迁移到 ES6 加载器时需自行验证详见 library-structures.md 的脚注部分。六、实战演练为magic-string-time编写声明文件把上述知识串起来我们按模板为文档示例中的magic-string-time编写一份完整的magic-string-time/index.d.ts// Type definitions for magic-string-time 1.0.0 // Project: https://example.com/magic-string-time // Definitions by: Your Name https://example.com/your-profile /*~ 该库导入时会向 String.prototype 和 Array.prototype 注入新方法 *~ 因此使用 global-modifying-module 模板。 */ declare global { interface String { /** 判断字符串是否以 Hello 开头 */ startsWithHello(): boolean; } interface ArrayT { /** 原地反转数组后排序 */ reverseAndSort(): T[]; } } /*~ 模块同时导出的配套类型与工具函数 */ export interface MagicStringTimeOptions { caseSensitive?: boolean; } export function configure(opts: MagicStringTimeOptions): void; /*~ 存在真实导出无需 export {} */关键步骤回顾按模板把文件命名为index.d.ts置于与模块同名的目录用declare global扩充String、Array等内置接口方法签名与运行时注入的实现保持一致含可选参数与返回类型模块自身的类型、工具函数用普通export导出因已有导出语句省略export {}。写完后再用tsc配合tsconfig.json做类型检查确认使用方代码require(magic-string-time)后能正确调用hello, world.startsWithHello()且不出现未定义方法报错。七、模板地图本模板在声明文件体系中的位置TypeScript 官方手册为声明文件准备了七种模板入口见 templates.md模块系module.d.ts普通模块、module-class.d.ts可构造、module-function.d.ts可调用、module-plugin.d.ts模块插件全局系global.d.ts全局代码库、global-plugin.d.ts全局插件、global-modifying-module.d.ts本文主题全局修改模块。选择模板的第一步永远是识别库的类型看文档如何使用、看源码是否含require/define/window赋值。识别方法论与完整示例见 library-structures.md声明文件编写总纲见 introduction.md。如果你正在为某个 npm 包补类型且它不属于全局修改场景直接阅读 module.d.ts 即可快速上手。赞分享文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载相关推荐TypeScript 声明文件模块模板 module.d.ts为模块化代码库编写类型定义的完整指南TypeScript 声明文件模块模板 module.d.ts为模块化代码库编写类型定义的完整指南 在 TypeScript 生态中为 JavaScript文档教程为类模块编写 TypeScript 声明文件module-class.d.ts 模板完全解析为类模块编写 TypeScript 声明文件module class.d.ts 模板完全解析 本指南以 module class.d.ts 模板 http文档教程TypeScript 声明文件模板深度解析为全局代码库编写 global.d.tsTypeScript 声明文件模板深度解析为全局代码库编写 global.d.ts 本文是 TypeScript 使用手册中文版声明文件·模板系列的技文档教程上一篇Coil圖像緩存過期策略TTL與LRU結合下一篇F2 小程序渲染原理与支付宝 / 微信小程序集成指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表