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

如何用TypeScript模板字面量类型实现对象键方法联合枚举与类型安全调用

实现类型安全的动态方法调用

需求背景

需要将rootStore中所有存储对象的键与对应方法名组合成联合类型(如'aStore.aMethod1'、'bStore.bMethod3'等),同时让dynamicCall函数具备类型安全性:

  • 限制action参数只能是合法的存储对象+方法名组合
  • 确保传入的参数类型与目标方法的参数类型完全匹配

解决方案

1. 定义合法的Action联合类型

通过模板字面量类型和映射类型遍历rootStore的结构,自动生成所有合法的store.method组合:

type ActionName<T> = {
  [StoreKey in keyof T]: `${StoreKey & string}.${keyof T[StoreKey] & string}`
}[keyof T];

这个类型会:

  • 遍历T的每个存储键(如aStore、bStore)
  • 对每个存储对象,遍历其所有方法键
  • 将存储键与方法键拼接成store.method格式的字符串
  • 最终生成所有合法组合的联合类型

2. 提取方法的参数类型

通过条件类型和类型推断,根据传入的action字符串,自动提取对应方法的参数类型:

type GetMethodParams<T, A extends ActionName<T>> = 
  A extends `${infer StoreKey}.${infer MethodKey}`
    ? StoreKey extends keyof T
      ? MethodKey extends keyof T[StoreKey]
        ? T[StoreKey][MethodKey] extends (...args: infer P) => any
          ? P
          : never
        : never
      : never
    : never;

该类型会逐层解析action字符串:

  1. 拆分出存储键StoreKey和方法键MethodKey
  2. 校验StoreKey和MethodKey的合法性
  3. 提取目标方法的参数类型数组

3. 改造dynamicCall函数

利用上述类型,让函数的参数类型完全受目标方法约束,同时优化内部类型断言:

function dynamicCall<T extends Record<string, Record<string, (...args: any[]) => any>>>(
  action: ActionName<T>,
  ...args: GetMethodParams<T, typeof action>
) {
  const [storeName, method] = action.split('.') as [keyof T, keyof T[keyof T]];
  const store = rootStore[storeName];
  
  if (store && method in store) {
    (store[method as keyof typeof store] as (...args: any[]) => any).apply(undefined, args);
  }
}

完整代码示例

const objA = {
  aMethod1: function (_params: string) {
    console.log("obj A a method1")
    return 'A1'
  },
  aMethod2: function (_params: string, _param2: string) {
    console.log("obj A a method2")
    return 'A2'
  }
}

const objB = {
  bMethod1: function (_params: string) {
    console.log("obj B bmethod1")
    return 'B1'
  },
  bMethod2: function (_params: string, _param2: string) {
    console.log("obj B bmethod2")
    return 'B2'
  },
  bMethod3: function (_params: string, _param2: string) {
    console.log("obj B bmethod3")
    return 'B3';
  }
}

const rootStore = {
  aStore: objA,
  bStore: objB,
}

// 生成合法的action联合类型
type ActionName<T> = {
  [StoreKey in keyof T]: `${StoreKey & string}.${keyof T[StoreKey] & string}`
}[keyof T];

// 根据action提取对应方法的参数类型
type GetMethodParams<T, A extends ActionName<T>> = 
  A extends `${infer StoreKey}.${infer MethodKey}`
    ? StoreKey extends keyof T
      ? MethodKey extends keyof T[StoreKey]
        ? T[StoreKey][MethodKey] extends (...args: infer P) => any
          ? P
          : never
        : never
      : never
    : never;

// 类型安全的动态调用函数
function dynamicCall<T extends Record<string, Record<string, (...args: any[]) => any>>>(
  action: ActionName<T>,
  ...args: GetMethodParams<T, typeof action>
) {
  const [storeName, method] = action.split('.') as [keyof T, keyof T[keyof T]];
  const store = rootStore[storeName];
  
  if (store && method in store) {
    (store[method as keyof typeof store] as (...args: any[]) => any).apply(undefined, args);
  }
}

// 合法调用示例
dynamicCall<typeof rootStore>('aStore.aMethod1', 'test'); // ✅ 参数符合aMethod1要求
dynamicCall<typeof rootStore>('bStore.bMethod2', 'foo', 'bar'); // ✅ 参数符合bMethod2要求

// 错误调用示例(TypeScript会报错)
// dynamicCall<typeof rootStore>('aStore.invalidMethod'); // ❌ 方法不存在
// dynamicCall<typeof rootStore>('aStore.aMethod1', 123); // ❌ 参数类型不匹配
// dynamicCall<typeof rootStore>('bStore.bMethod3', 'onlyOneParam'); // ❌ 参数数量不足

内容的提问来源于stack exchange,提问作者Mr.Trieu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 15:10:29