Typescript仅提取类可调用成员类型并实现带上下文的命令模式
解决方案
核心思路
通过TS泛型工具过滤类中的非可调用成员,定义强类型的命令结构绑定实例上下文,同时完整透传方法的参数与返回值类型,满足类型校验、上下文保留、返回值透传三个核心需求。
实现步骤
1. 定义类型工具提取类的可调用成员
该工具会自动过滤类中的普通属性,仅保留方法类型的键与对应函数签名:
type ExtractMethods<T> = { [K in keyof T as T[K] extends (...args: any[]) => any ? K : never]: T[K] }
2. 定义强类型命令结构
命令结构绑定实例、方法名、参数,同时预定义带正确返回值的执行方法:
type ClassCommand<T, M extends keyof ExtractMethods<T>> = { instance: T methodName: M args: Parameters<ExtractMethods<T>[M]> execute: () => ReturnType<ExtractMethods<T>[M]> }
3. 实现命令创建工厂函数
工厂函数会自动做类型校验,仅允许传入类的合法方法名,同时绑定实例上下文保证this指向正确:
function createCommand<T, M extends keyof ExtractMethods<T>>( instance: T, methodName: M, ...args: Parameters<ExtractMethods<T>[M]> ): ClassCommand<T, M> { return { instance, methodName, args, // 箭头函数绑定实例上下文,避免this丢失 execute: () => instance[methodName](...args) } }
4. 实现保留返回值的重试包装函数
泛型定义的重试函数会自动继承命令执行方法的返回值类型,不会出现类型丢失:
async function callWithRetry<R>( command: { execute: () => R }, retryTimes = 3 ): Promise<Awaited<R>> { let lastError: Error for (let i = 0; i < retryTimes; i++) { try { // 自动适配同步/异步方法返回值 return await Promise.resolve(command.execute()) } catch (e) { lastError = e as Error // 指数退避等待,可按需调整 await new Promise(resolve => setTimeout(resolve, 100 * Math.pow(2, i))) } } throw lastError! }
效果验证
用包含普通属性的示例类测试:
class MyClass { // 普通非可调用属性 public version = '1.0.0' public count = 0 // 可调用方法 add(a: number, b: number): number { this.count += 1 return a + b } async fetchData(id: string): Promise<{ id: string; value: number }> { this.count += 1 return { id, value: Math.random() } } } const inst = new MyClass() // 合法场景:创建add方法命令 const addCmd = createCommand(inst, 'add', 1, 2) console.log(addCmd.execute()) // 输出3,count变为1,this指向正确,返回值类型为number // 合法场景:命令传入重试函数,返回值类型完整保留 const fetchCmd = createCommand(inst, 'fetchData', 'test-id') // fetchRes类型为 { id: string; value: number } const fetchRes = await callWithRetry(fetchCmd) // 非法场景1:选择普通属性作为方法,TS直接抛出类型错误 // @ts-expect-error 类型"version"无法赋值给类型"add" | "fetchData" const invalidCmd = createCommand(inst, 'version') // 非法场景2:参数不匹配触发类型校验 // @ts-expect-error 类型"string"无法赋值给类型"number" const wrongArgsCmd = createCommand(inst, 'add', '1', 2)
内容的提问来源于stack exchange,提问作者Komi
相关产品推荐
相关产品推荐

