我正在创建这个类(playground link):

export class CascadeStrategies<
  T extends Record<any, (...args: any[]) => unknown>
> {
  private strategies: T = {} as T;

  constructor(strategyMap: T) {
    this.registerStrategies(strategyMap);
  }

  private registerStrategies(strategyMap: T) {
    this.strategies = strategyMap;
  }

  use(
    strategies: (keyof T)[],
    ...args: Parameters<T[keyof T]>
  ): ReturnType<T[keyof T]> {
    return this.strategies[strategies[0]](...args);
  }
}

此类的预期用途应为

const myMap = {
  test: (arg1: number, arg2: string) => arg1,
  otherTest: (arg1: number, arg2: string) => arg2,
  thirdTest: (arg1: number, arg2: string) => null
}
const cascadeStrats = new CascadeStrategies(myMap);
const shouldBeNumber = cascadeStrats.use(["test"], 0, "");
const shouldBeString = cascadeStrats.use(["otherTest"], 0, "");
const shouldBeNull = cascadeStrats.use(["thirdTest"], 0, "");

I want T to be an object whose entries are functions that can accept the same set of parameters and returns a string, so I am using T extends Record<any, (...args: unknown[]) => string.
With this typing, this.strategies[strategies[0]](...args) has type unknown which is incompatible with the expected ReturnType<T[keyof T]>.

如果我将strategies的类型从T更改为Record<any, any>this.strategies[strategies[0]](...args)将具有正确的类型,并且在使用时正确推断.即使strategies只是一个内部变量,在使用类时不会影响DX,我想知道我在这里缺少什么来实现预期的结果:

  • 用户定义strategyMap时正确推断(即,其条目是接受相同参数集并返回string的函数的对象).
  • strategies不是Record<any, any>类型
  • 当用户使用cascadeStrats.use时,他在函数的参数和返回的类型中得到了正确的推断.

推荐答案

我认为表达这一点的最直接的方法是将generic个类型参数分成两个. 你可以有A,所有策略都通用的参数列表类型,T,从策略键到相应策略的返回类型的映射. 给定这些类型,那么strategies就是

type Strategies<A extends any[], T> =
  { [K in keyof T]: (...args: A) => T[K] }

它是一个将T中的每个成员转换成返回该成员的函数的mapped type.

以下是我对CascadeStrategies的概率:

class CascadeStrategies<A extends any[], T extends object> {
  private strategies!: Strategies<A, T>

  constructor(strategyMap:
    Record<string, (...args: A) => any> &
    Strategies<A, T>
  ) {
    this.registerStrategies(strategyMap);
  }

  private registerStrategies(strategyMap: Strategies<A, T>) {
    this.strategies = strategyMap;
  }

  use<K extends keyof T>(
    strategies: K[],
    ...args: A
  ) {
    return this.strategies[strategies[0]](...args); // okay
  }
}

编译时没有错误.这里最重要的部分是use()里面发生的事情. 现在这个函数在K中是泛型的,键为T. 返回类型use()根据需要推断T[K].

请注意,为了使其工作,我们需要TypeScript在编写new CascadeStrategies(myMap)时推断出AT. 推理可能很棘手.我的方法是使构造函数参数的类型为Record<string, (...args: A) => any> & Strategies<A, T>. 这是一个intersection,其中每个片段帮助推断出一个不同类型的论点. Record<string, (...args: A) => any>类型允许推断A,因为它可以在TypeScript知道T之前发生. 然后Strategies<A, T>允许从方法的返回类型推断T. 将交叉点X & Y加宽到其成员Y中的一个总是安全的,所以我们可以处理复杂的交叉点,并将strategyMap作为类型Strategies<A, T>来处理.


让我们来测试一下:

const myMap = {
  test: (arg1: number, arg2: string) => arg1,
  otherTest: (arg1: number, arg2: string) => arg2,
  thirdTest: (arg1: number, arg2: string) => null
}
const cascadeStrats = new CascadeStrategies(myMap);
/*    ^? const cascadeStrats: CascadeStrategies<
    [arg1: number, arg2: string], 
    { test: number; otherTest: string; thirdTest: null; }
    >
*/
const shouldBeNumber = cascadeStrats.use(["test"], 0, "");
//    ^? const shouldBeNumber: number
const shouldBeString = cascadeStrats.use(["otherTest"], 0, "");
//    ^? const shouldBeString: string
const shouldBeNull = cascadeStrats.use(["thirdTest"], 0, "");
//    ^? const shouldBeNull: null
const shouldBeStringOrNumber = cascadeStrats.use(["test", "otherTest"], 0, "")
//    ^? const shouldBeStringOrNumber: string | number

cascadeStrats.use(["test"], "oops", "abc"); // error!
// -----------------------> ~~~~~~
// Argument of type 'string' is not assignable to parameter of type 'number'.

看起来不错T被正确推断,因此shouldBeXXX都是预期的类型,A也被正确推断,以便编译器注意到如果传入了错误的参数类型(如上面最后一行所示).

Playground link to code

Typescript相关问答推荐

在React中实现具有独立页面的嵌套路径的最佳实践是什么?

需要使用相同组件重新加载路由变更数据—React TSX

如何判断输入是否是TypeScript中的品牌类型?

如何使用泛型类型访问对象

获取函数中具有动态泛型的函数的参数

我可以为情态车使用棱角形的路由守卫吗?

使用Dockerfile运行Vite React应用程序时访问env变量

ANGLING v17 CLI默认设置为独立:延迟加载是否仍需要@NgModule进行路由?

函数重载中的不正确TS建议

FatalError:Error TS6046:';--模块分辨率';选项的参数必须是:'; node ';,'; node 16';,'; node

React router 6(数据路由)使用数据重定向

使用ngfor循环时,数据未绑定到表

创建一个函数,该函数返回一个行为方式与传入函数相同的函数

在打印脚本中将对象类型转换为数组

如何使用angular15取消选中位于其他组件中的复选框

有没有一种方法可以提升对象的泛型级别,而对象的值是泛型函数?

如何使用useSearchParams保持状态

T的typeof键的Typescript定义

React router - 如何处理嵌套路由?

Typescript 编译错误:TS2339:类型string上不存在属性props