TypeScript声明文件中为导出对象添加动态属性的解决方案
问题背景
我正尝试向DefinitelyTyped仓库提交PR,为一个JavaScript库创建对应的@types类型包,待编写类型的JavaScript库源码结构如下:
const COMMANDS = [ "about", "authorize", "backend", "cat", "check", "checksum", "cleanup", "config", "config create", "config delete", "config disconnect", "config dump", "config edit", "config file", "config password", "config providers", /* 此处省略大量其余命令 */ ]; const api = function(...args) { // 省略无关实现逻辑 } const promises = api.promises = function(...args) { // 省略无关实现逻辑 } COMMANDS.forEach(command => { Object.defineProperty(api, command, { value: function(...args) { return api(command, ...args); } }); Object.defineProperty(api.promises, command, { value: function(...args) { return promises(command, ...args); } }); }); module.exports = api;
强制约束:由于该库使用
module.exports语法导出,DefinitelyTyped校验规则要求类型声明必须使用*export = api语法,禁止使用export default api*语法,所有方案必须满足该要求。
目前已尝试编写的声明文件代码如下:
/// <reference types="node" /> import { ChildProcess } from "child_process"; declare const COMMANDS: [ "about", "authorize", "backend", "cat", "check", "checksum", "cleanup", "config", "config create", "config delete", "config disconnect", "config dump", "config edit", "config file", "config password", "config providers", /* 此处省略大量其余命令 */ ]; type CommandsUnion = typeof COMMANDS[number]; declare function api( ...args: (string | Object)[] ): ChildProcess; declare namespace api { type PromiseApiFn = ( ...args: (string | Object)[] ) => Promise<Buffer>; const promises: PromiseApiFn & { [Command in CommandsUnion]: PromiseApiFn }; } export = api;
当前核心阻塞问题:上述写法仅完成了api.promises上对应命令属性的类型声明,无法将COMMANDS数组中对应的命令属性正确挂载到api函数对象本身的类型上,需要可行的实现方案。
可行实现方案
直接使用交叉类型合并函数调用签名与所有静态挂载属性即可,不需要依赖declare function + declare namespace的声明合并逻辑,最终类型定义代码如下:
/// <reference types="node" /> import { ChildProcess } from "child_process"; // 补全所有命令到联合类型中即可 type CommandsUnion = | "about" | "authorize" | "backend" | "cat" | "check" | "checksum" | "cleanup" | "config" | "config create" | "config delete" | "config disconnect" | "config dump" | "config edit" | "config file" | "config password" | "config providers"; // 此处继续补充剩余省略的命令 // 基础调用签名类型 type BaseSyncFn = (...args: (string | object)[]) => ChildProcess; type BaseAsyncFn = (...args: (string | object)[]) => Promise<Buffer>; // 合并api所有类型:同步调用签名 + 顶层命令方法 + promises异步API declare const api: BaseSyncFn & { [Command in CommandsUnion]: BaseSyncFn; } & { promises: BaseAsyncFn & { [Command in CommandsUnion]: BaseAsyncFn; }; }; export = api;
方案说明:
- 完全符合
export = api的导出要求,不会触发DefinitelyTyped的格式校验错误 - 支持所有调用场景的类型提示:直接调用
api(xxx)、调用顶层命令方法如api.about(xxx)、调用异步入口api.promises(xxx)、调用异步命令方法如api.promises.about(xxx)都能得到正确的类型推导 - 原写法中大写的
Object类型替换为TS推荐的小写object类型,避免不必要的类型兼容问题 - 不需要单独声明元组类型的
COMMANDS常量,直接维护命令联合类型即可,减少冗余代码
内容的提问来源于stack exchange,提问作者atifcppprogrammer
相关产品推荐
相关产品推荐

