TypeScript类型定义:如何让UMD模块的枚举通过命名空间访问?
刚好我之前也处理过类似的UMD模块类型定义问题,来帮你理清楚前因后果和解决办法:
原方案失效的核心原因
你第一次在.d.ts里直接声明枚举的问题,本质是TypeScript对.d.ts文件的处理逻辑:.d.ts是纯类型描述文件,TS编译器只会从中提取类型信息,不会把里面的枚举、类等编译成实际的JavaScript代码。
原库是纯JS的UMD模块,本身没有MouseButton这个对象,而你的.d.ts里的枚举只是给TS看的类型提示,运行时浏览器里根本不存在myLib.MouseButton,自然会抛出Cannot read property 'LEFT' of undefined的错误。
实现import * as myLib直接访问枚举的可行方案
要让枚举既能在编译时提供类型提示,又能在运行时通过UMD模块访问,有两种实用的方式:
方式1:给UMD模块补充枚举的运行时实现
既然原库是UMD结构,我们可以写一个补充的UMD文件,把枚举作为模块的一部分暴露出来,再搭配类型定义:
- 创建一个
myLib-enums.js文件(用于提供运行时枚举):
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.myLib = global.myLib || {}))); }(this, (function (exports) { // 把枚举做成普通JS对象暴露 exports.MouseButton = { LEFT: 1, MIDDLE: 2, RIGHT: 4 }; })));
- 对应的
index.d.ts类型定义:
export as namespace myLib; // 声明枚举类型,和JS对象的结构对应 export enum MouseButton { LEFT = 1, MIDDLE = 2, RIGHT = 4 } export function activate(button: MouseButton): void;
之后你只要在项目中引入这个myLib-enums.js(和原cornerstone库一起加载),就能通过import * as myLib from 'myLib'同时拿到类型提示和运行时可访问的myLib.MouseButton了。
方式2:用「类型别名+常量对象」替代枚举(更轻量)
如果不想额外写JS文件,也可以用TS的类型特性模拟枚举效果,同时保留运行时可用性:
- 在
index.d.ts中定义类型和值的声明:
export as namespace myLib; // 声明运行时可访问的常量对象类型 export const MouseButton: { readonly LEFT: 1; readonly MIDDLE: 2; readonly RIGHT: 4; }; // 把对象的属性值作为类型,方便参数约束 export type MouseButton = typeof MouseButton[keyof typeof MouseButton]; export function activate(button: MouseButton): void;
- 在项目的初始化代码里(或者单独的工具文件)添加运行时的常量对象:
// 把MouseButton挂载到全局的myLib对象上 window.myLib = window.myLib || {}; window.myLib.MouseButton = { LEFT: 1, MIDDLE: 2, RIGHT: 4 };
这种方式的好处是不需要依赖TS的枚举编译,完全贴合纯JS UMD模块的场景,MouseButton既是类型(用于约束activate的参数),又是可访问的对象(运行时用)。
关于你拆分文件方案的问题
你之前把枚举放到单独的MouseButton.ts再导入到.d.ts的做法,会让index.d.ts变成模块类型定义,而不是全局命名空间的一部分。TS不会把.ts里的枚举自动打包进UMD模块的全局对象中,所以myLib.MouseButton只有编译时类型有效,运行时找不到对应的对象。
内容的提问来源于stack exchange,提问作者t.animal

