TypeScript无法从类模板参数推导所需返回类型的原因及解决方案
我来帮你拆解这个TypeScript类型推断的问题,并给出适配你去中心化Hook框架需求的解决方案。
问题根源
第一个示例的问题
你定义的on函数把ArgType、ReturnType和T作为三个独立泛型参数声明,但TypeScript无法在instanceof分支里将泛型参数ReturnType与PostCreate的具体返回类型number关联起来。虽然t被细化为PostCreate实例,但泛型参数ReturnType仍然保持着初始的约束(未被细化),所以TypeScript认为number无法赋值给未知的ReturnType。
第二个示例的问题
使用infer的条件类型InferReturnType<T>是基于泛型T的,而在函数内部的分支里,即使t被细化为PostCreate,泛型T依然是整个函数的通用参数,TypeScript无法在分支内解析InferReturnType<T>的具体值,导致它始终被视为unknown。
去中心化解决方案
方案1:基于泛型推断的灵活实现(推荐)
调整泛型声明方式,让返回类型直接从传入的Hook实例类型中推断,配合类型断言解决分支内的类型匹配问题:
abstract class Hook<ArgsType, ReturnType = void> { constructor(public readonly args: ArgsType){} } // 独立定义Hook类,完全去中心化 class PostCreate extends Hook<{key: string}, number>{} class PreUpdate extends Hook<{id: number}, boolean>{} class PostDelete extends Hook<{uuid: string}, void>{} function on<T extends Hook<any, any>>(t: T): T extends Hook<any, infer R> ? R | undefined : undefined { if (t instanceof PostCreate) { console.log(t.args.key); // 正确识别args类型 // 基于当前t的具体类型断言返回值 return 5 as typeof t extends Hook<any, infer R> ? R : never; } else if (t instanceof PreUpdate) { console.log(t.args.id); return true as typeof t extends Hook<any, infer R> ? R : never; } else if (t instanceof PostDelete) { console.log(t.args.uuid); return undefined as typeof t extends Hook<any, infer R> ? R : never; } return undefined; } // 调用时自动推断返回类型 const postResult = on(new PostCreate({key: "my value"})); // number | undefined const updateResult = on(new PreUpdate({id: 123})); // boolean | undefined const deleteResult = on(new PostDelete({uuid: "abc"})); // void | undefined
优点:完全去中心化,新增Hook类只需添加对应的instanceof分支即可,无需修改全局类型定义。
方案2:函数重载实现精准类型推断
如果你的Hook类型数量相对固定,可以用函数重载实现最精准的类型推断:
abstract class Hook<ArgsType, ReturnType = void> { constructor(public readonly args: ArgsType){} } class PostCreate extends Hook<{key: string}, number>{} class PreUpdate extends Hook<{id: number}, boolean>{} // 为每个Hook类型定义重载签名 function on(t: PostCreate): number | undefined; function on(t: PreUpdate): boolean | undefined; // 通用实现 function on(t: Hook<any, any>): any | undefined { if (t instanceof PostCreate) { console.log(t.args.key); return 5; } else if (t instanceof PreUpdate) { console.log(t.args.id); return true; } return undefined; } // 类型推断完全精准 const postResult = on(new PostCreate({key: "test"})); // number | undefined const updateResult = on(new PreUpdate({id: 123})); // boolean | undefined
优点:类型推断最精准,无需类型断言;缺点:新增Hook类需要手动添加对应的重载签名,扩展性稍弱。
方案3:类型谓词辅助的类型细化
通过自定义类型谓词,让TypeScript更精准地识别Hook类型及其返回值:
abstract class Hook<ArgsType, ReturnType = void> { constructor(public readonly args: ArgsType){} } // 自定义类型谓词,用于细化Hook的返回类型 function isHookType<T extends Hook<any, any>>( t: Hook<any, any>, ctor: new (...args: any[]) => T ): t is T { return t instanceof ctor; } class PostCreate extends Hook<{key: string}, number>{} class PreDelete extends Hook<{id: number}, string>{} function on<T extends Hook<any, any>>(t: T): T extends Hook<any, infer R> ? R | undefined : undefined { if (isHookType(t, PostCreate)) { console.log(t.args.key); return 5 as T extends Hook<any, infer R> ? R : never; } else if (isHookType(t, PreDelete)) { console.log(t.args.id); return "deleted" as T extends Hook<any, infer R> ? R : never; } return undefined; } const postResult = on(new PostCreate({key: "test"})); // number | undefined const deleteResult = on(new PreDelete({id: 456})); // string | undefined
优点:类型谓词可复用,适合复杂场景下的类型细化;缺点:需要额外定义类型谓词函数。
内容的提问来源于stack exchange,提问作者Pinna_be

