带泛型索引的嵌套类型索引访问生成无效不透明类型问题
问题描述
我用单个大型嵌套类型定义类RPC接口,结构按命名空间→方法名→参数依次嵌套:
interface IndexedActions { foo: { nested: { parameter: string; }; }; bar: { second: { argument: number; }; }; }
为了实现类型安全的方法调用,我定义了如下函数签名:
function invoke<N extends keyof IndexedActions, M extends keyof IndexedActions[N]>( namespace: N, method: M, args: IndexedActions[N][M] );
调用端完全符合预期:能正确校验参数类型,还能根据前置参数自动补全后续参数。但在实现函数时,编译器无法识别args是对象类型——尽管我的嵌套类型全是对象,报错信息如下:
Type 'IndexedActions[N][M]' is not assignable to type 'object'. Type 'IndexedActions[N][keyof IndexedActions[N]]' is not assignable to type 'object'. Type 'IndexedActions[N][string] | IndexedActions[N][number] | IndexedActions[N][symbol]' is not assignable to type 'object'. Type 'IndexedActions[N][string]' is not assignable to type 'object'.
我试过这些方法,但都没解决问题:
- 将
keyof与& string交叉(移除报错中的symbol类型) - 放弃泛型,改用
invoke(namespace: keyof IndexedActions, method: keyof IndexedActions[typeof namespace], ...)这类签名 - 用
T extends T ? ...语法强制分发类型,改善了method的类型但没解决整体问题 - 混合使用上述方法
怎么给这个函数的实现正确定义类型?
解决方案
核心问题是:TypeScript编译器只看泛型约束,无法从IndexedActions的实际定义中推导IndexedActions[N][M]一定是对象。以下是几种可行的解决方式:
方式1:通过泛型约束强制参数为对象
修改泛型约束,明确要求IndexedActions[N][M]必须是对象类型,同时保留调用端的自动补全能力:
function invoke< N extends keyof IndexedActions, M extends keyof IndexedActions[N] & (IndexedActions[N][M] extends object ? string : never) >( namespace: N, method: M, args: IndexedActions[N][M] ) { // 此处可直接将args当作object使用,无需额外断言 console.log(args); // 你的实现逻辑 }
方式2:在实现中使用类型断言
如果不想修改调用端的签名,最简单的方式是在实现里用类型断言明确告诉编译器args是对象:
function invoke<N extends keyof IndexedActions, M extends keyof IndexedActions[N]>( namespace: N, method: M, args: IndexedActions[N][M] ) { const objArgs = args as object; // 后续使用objArgs进行操作 }
方式3:提前定义约束工具类型
先定义一个工具类型,确保所有命名空间下的方法参数都是对象,再基于这个类型定义invoke:
// 工具类型:强制每个方法的参数都是对象 type TypedActionMap = { [Namespace in keyof IndexedActions]: { [Method in keyof IndexedActions[Namespace]]: object & IndexedActions[Namespace][Method] } }; function invoke<N extends keyof TypedActionMap, M extends keyof TypedActionMap[N]>( namespace: N, method: M, args: TypedActionMap[N][M] ) { // 此处args可直接当作object处理 }
三种方式各有侧重:方式1最严谨,兼顾调用端体验和实现端类型安全;方式2快速直接;方式3适合需要全局约束所有RPC方法参数的场景。
内容的提问来源于stack exchange,提问作者skelley
相关产品推荐
相关产品推荐

