如何为TypeScript中动态生成方法的Shiinobi类添加类型定义?
解决TypeScript无法识别动态生成类方法的问题
要让TypeScript识别动态添加的类方法,核心是让TS能推断出这些方法的具体名称和类型,以下是可行的实现方案:
步骤1:固定命令数组的类型
首先给COMMANDS添加as const断言,让TypeScript推断出数组中每个元素的具体字符串字面量类型,而非宽泛的string类型:
const COMMANDS = [ "get-myanimelist-staff-urls", "get-myanimelist-anime-explicit-genres", "get-myanimelist-anime-genres", "get-myanimelist-anime-themes" // ...其他命令 ] as const;
步骤2:定义类型映射工具
创建辅助类型,将连字符格式的命令名转换为下划线格式的方法名,并生成对应方法的类型:
// 把连字符替换为下划线的递归类型 type ReplaceDashes<S extends string> = S extends `${infer P}-${infer R}` ? `${P}_${ReplaceDashes<R>}` : S; // 生成所有动态方法的类型 type ShiinobiCommand = typeof COMMANDS[number]; type ShiinobiMethods = { // 每个命令对应一个async方法,接收可选id参数,返回Promise [Cmd in ShiinobiCommand as ReplaceDashes<Cmd>]: (id?: string | number) => Promise<unknown>; };
步骤3:通过工厂函数生成类型安全的实例
使用工厂函数合并基础类和动态方法类型,避免手动声明每个方法:
class ShiinobiBase { // 原私有spawn方法保持不变 async #spawn(options: { command: string; id?: string | number }) { // 这里写你的spawn逻辑,比如调用外部服务等 return {}; } // 生成所有动态方法的辅助函数 #buildMethods() { const methods: Partial<ShiinobiMethods> = {}; COMMANDS.forEach((command) => { const methodName = command.replaceAll("-", "_") as keyof ShiinobiMethods; methods[methodName] = async (id?: string | number) => { return this.#spawn({ command, id }); }; }); return methods; } // 暴露给外部的创建入口 static create(): ShiinobiBase & ShiinobiMethods { const instance = new ShiinobiBase(); return Object.assign(instance, instance.#buildMethods()) as ShiinobiBase & ShiinobiMethods; } }
步骤4:类型安全地使用实例
调用工厂函数创建实例后,TypeScript就能自动识别所有动态生成的方法,不会再抛出错误:
async function useShiinobi() { const shiinobi = ShiinobiBase.create(); // 这里有完整的类型提示,无TS错误 const staffUrls = await shiinobi.get_myanimelist_staff_urls("123"); const genres = await shiinobi.get_myanimelist_anime_genres(456); }
备选方案:类内手动声明方法类型
如果不想用工厂函数,也可以在类内通过declare关键字手动声明每个动态方法的类型,配合类型断言确保赋值安全:
class Shiinobi implements ShiinobiMethods { constructor() { COMMANDS.forEach((command) => { const methodName = command.replaceAll("-", "_") as keyof ShiinobiMethods; this[methodName] = async (id?: string | number) => { return this.#spawn({ command, id }); }; }); } // 声明所有动态方法的类型,让TS识别它们 declare get_myanimelist_staff_urls: (id?: string | number) => Promise<unknown>; declare get_myanimelist_anime_explicit_genres: (id?: string | number) => Promise<unknown>; declare get_myanimelist_anime_genres: (id?: string | number) => Promise<unknown>; declare get_myanimelist_anime_themes: (id?: string | number) => Promise<unknown>; // ...其他命令对应的声明 async #spawn(options: { command: string; id?: string | number }) { // 原spawn逻辑 return {}; } } // 使用方式不变 async function test() { const shiinobi = new Shiinobi(); const res = await shiinobi.get_myanimelist_staff_urls(); }
内容的提问来源于stack exchange,提问作者moonlitgrace
相关产品推荐
相关产品推荐

