typescript 从与特定对象的键关联的对象推断特定类型

bqujaahr  于 2022-12-05  发布在  TypeScript
关注(0)|答案(1)|浏览(202)

我有一个{[key]: <T>()=>T}形状的注册表对象,T可以是任何东西,因为我想在将来轻松地添加更多的条目。然后我有一个函数接受注册表键作为字符串,这个函数应该返回一个关联的泛型类型的值。但是我不能让它工作,因为它返回注册表中所有函数返回的泛型类型的并集。
这个描述有点笨拙,所以我希望这个简单的工作示例足够详细地描述我打算做的事情。

type valFn<T> = () => T

declare const stringFn: valFn<string>;
declare const numberFn: valFn<number>;

const registry = {
  alpha: stringFn,
  beta: numberFn,
  // ... i want to easily add some records in the future without modifying +registryEater+ every time i do so, all the records will be of type +key: ValFn<Something>+
};

// CHANGES CAN BE MADE ONLY DOWN FROM HERE
type ReturnType<T> = T extends valFn<infer R> ? R : never
const registryEater = <T extends keyof typeof registry>(registryKey: T): ReturnType<typeof registry[T]> => {
  const desiredFn = registry[registryKey];
  const desiredValue = desiredFn();
  // TS2322 because +desiredValue+ is string | number, but i need it do be a specific type depending on real value of +registryKey+
  return desiredValue;
};
// CHANGES CAN BE MADE ONLY UP FROM HERE

const thisIsString = registryEater('alpha');
const thisIsNumber = registryEater('beta');

我可以通过将return desiredValue;更改为return desiredValue as ReturnType<typeof registry[T]>;来使其工作,但是没有更干净的方法吗?

2skhul33

2skhul331#

编译器不能对依赖于generic类型参数的conditional types(如ReturnType<typeof registry[T]>)进行类型分析。如果你想以编译器“理解“的方式编写函数,你应该重构为使用microsoft/TypeScript#47109中描述的 * 分布式对象类型 *。其思想是从一个非常基本的Map类型开始,如

interface RegistryReturn {
    alpha: string;
    beta: number;
}

然后将其他操作表示为mapped types

type Registry = { [K in keyof RegistryReturn]: () => RegistryReturn[K] }
const registry: Registry = {
    alpha: stringFn,
    beta: numberFn
}

然后,您的泛型函数可以只Map类型index into,这样就可以正常工作了:

const registryEater = <K extends keyof RegistryReturn>(
    registryKey: K
): RegistryReturn[K] => {
    const desiredFn = registry[registryKey];
    const desiredValue = desiredFn();
    return desiredValue; // okay
};

desiredFn变量被视为Registry[K]类型,然后desiredValue()被视为RegistryReturn[K]类型,具体原因是Registry被定义为Map类型,如microsoft/TypeScript#47109中所述。
这样就可以编译了,但是不幸的是它用Registry重新定义了registry,这是不允许的。相反,你的要求是registry是给你的,不能被修改。幸运的是我们可以用它来定义RegistryReturnMap类型:

type RegistryReturn = { [K in keyof typeof registry]: 
  ReturnType<typeof registry[K]> 
}

然后将registry赋给一个Registry类型的新变量,这样Map的类型查找仍然会发生:

const registryEater = <K extends keyof RegistryReturn>(
    registryKey: K
): RegistryReturn[K] => {
    const _registry: Registry = registry; // new variable
    const desiredFn = _registry[registryKey];
    const desiredValue = desiredFn();
    return desiredValue; // still okay
};

这样仍然可以编译。剩下要做的唯一事情是确保调用者获得他们想要的行为:

const thisIsString: string = registryEater('alpha'); // okay
const thisIsNumber: number = registryEater('beta'); // okay

看起来不错!
Playground代码链接

相关问题