You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

带泛型索引的嵌套类型索引访问生成无效不透明类型问题

问题描述

我用单个大型嵌套类型定义类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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 06:36:08