typescript 从联合中提取可调用键时,未知类型没有调用签名

2ic8powd  于 2023-10-22  发布在  TypeScript
关注(0)|答案(1)|浏览(258)

我试图在我们的代码库中输入一个遗留函数,它调用某些对象的方法(并应用错误处理等)。我下面的部分(玩具)解决方案可以约束action类型,但不能给予准确的返回类型(返回any)。
entityGetter函数应该接受entityA或entityB,并且只接受以'get'开始的开始可调用键。这后一部分工作正常,这告诉我,获得正确的返回值必须是可能的;但是我不明白为什么action在访问entity时没有正确的类型。
我试过给T和A分配默认值,但似乎什么也没改变。
有没有可能在不对entityGetter函数的(运行时)接口进行任何更改的情况下使其工作?理想情况下,根本没有JS更改,但允许对函数体进行一些更改。

type TEntityA = {
  getA: () => string;
  getB: () => string;
  setC: (arg0: string) => string;
  valD: number;
  getE: () => object;
};
type TEntityB = {
  getA: () => string;
  getB: () => number;
  setC: (arg0: string) => string;
  valD: string;
};
type TEntities = TEntityA | TEntityB;

// type KeysOfType = https://stackoverflow.com/a/64254114

type TGetterKeys<T extends TEntities> = keyof Pick<T, keyof T & (`get${string}`)>;
type TCallableKeys<T extends TEntities> = KeysOfType<T, () => unknown>;
type TGetters<T extends TEntities> = TGetterKeys<T> & TCallableKeys<T>;

const entityGetter = <
  T extends TEntities,
  A extends TGetters<T>
>(action: A, entity: T) => entity[action]();
// This expression is not callable. Type 'unknown' has no call signatures.

const entityA = new EntityA();
const entityB = new EntityB();

const AgetA = entityGetter('getA', entityA); // => string
const BgetB = entityGetter('getB', entityB); // => number

const AgetX = entityGetter('getX', entityA);
// Argument of type '"getX"' is not assignable to parameter of type '"getA" | "getB" | "getE"'.

操场

vsnjm48y

vsnjm48y1#

TS不明白T[A]总是某种函数。所以首先我们需要说服(entity[action] as Function)()来确保它是可调用的。但是ReturnType<Function>any,所以我们还必须为entityGetter定义ReturnType。
出于与上述相同的原因,简单的ReturnType<T[A]>不起作用。TS无法预测 (在这个通用场景中,在使用显式值和类型调用它之前)T[A]是可调用的,所以它拒绝这样做。所以我们必须手动操作:T[A] extends () => infer R? R: never。这现在对于TS是可预测的。
我还总结了TGetters

type TGetters<T> = {
  [K in keyof T]: T[K & `${"get" | "is"}${string}`] extends (...args: any) => any ? K : never;
}[keyof T];

const entityGetter = <
  T extends TEntities,
  A extends TGetters<T>
>(action: A, entity: T): T[A] extends () => infer R ? R : never =>
  (entity[action] as Function)();

type KeysOfType<T, U> = keyof T & keyof {
  [K in keyof T as T[K] extends U ? K : never]: 1
};

const entityGetter = <
  T extends TEntities,
  A extends KeysOfType<T, () => unknown> & `${"get" | "is"}${string}`
>(action: A, entity: T): T[A] extends () => infer R ? R : never =>
  (entity[action] as Function)();

TSPlayground

子节点:T extends TEntities作为一个保护,阻止你使用任何不是TEntityA | TEntityB的对象调用这个函数,即使遵循一般模式 (对象的getter函数以getis开头)。对于键入一个简单的T就足够了。

相关问题