请考虑以下事项:

  type ValueMap = {
    foo: 'FOO1' | 'FOO2';
    bar: 'BAR1' | 'BAR2' | 'BAR3';
  };
  type Key = keyof ValueMap;

  type Value<K extends Key> = ValueMap[K];

  type Entry<K extends Key> = {
    key: K;
    value: Value<K>;
  }

换句话说,Entrykeyvalue组成,允许的值集取决于密钥,因此例如

{key: 'foo', value: 'FOO2'} - valid
{key: 'foo', value: 'BAR3'} - invalid
{key: 'bar', value: 'BAR1'} - valid
{key: 'bar', value: 'FOO2'} - invalid

现在,我想要将一组这样的条目发送到一个函数,并且希望从that element's key推断出每个条目的类型.但是,如果我以数组的形式执行此操作,则不能很好地工作:

  function sendArray<K extends Key>(v: Array<Entry<K>>) {
    ...
  }

  sendArray([{key: 'foo', value: 'FOO2'}, {key: 'bar', value: 'BAR1'}])
  sendArray([{key: 'foo', value: 'FOO2'}, {key: 'bar', value: 'FOO1'}]) // compiles :(

这里,sendArray被调用,K被推断为"foo" | "bar",并且每个数组元素仅被验证,使得key可被赋值给"foo" | "bar",value可被赋值给ValueMap<"foo" | "bar">,即来自foo的值对于bar个条目是有效的,反之亦然.:(

我知道,如果我改用记录类型,它会起作用:

  type Entries<K extends Key> = {
    [P in K]?: Value<P>;
  }

  function sendRecord<K extends Key>(v: EntryRecord<K>) {
  }

  sendRecord({foo: 'FOO2', bar: 'BAR3'})
  sendRecord({foo: 'FOO2', bar: 'FOO1'}) // does not compile! :) 

...这很好,但我真的希望这对数组起作用.有没有办法这样做,从而推断出第一个数组元素是Entry<"foo">,而第二个元素是Entry<"bar">

推荐答案

你在这里根本不需要泛型,但是如果你出于某种原因在这个问题之外仍然需要它们,我们仍然必须使Entry个非泛型.所需类型为:

{
    key: "foo";
    value: "FOO1" | "FOO2";
} | {
    key: "bar";
    value: "BAR1" | "BAR2" | "BAR3";
}

为了实现这一目标,我们将使用mapped types:

type Entry = {
  [K in Key]: {
    key: K;
    value: ValueMap[K];
  };
}[Key];

然后,在sendArray中,我们必须修改仿制药以接受Entry[],而不是Key.为了确保只读数组也被接受,我们将在约束中添加readonly:

function sendArray<E extends readonly Entry[]>(v: E) {}

测试:

sendArray([
  { key: "foo", value: "FOO2" },
  { key: "bar", value: "BAR1" },
]);
sendArray([
  { key: "foo", value: "FOO2" },
  { key: "bar", value: "FOO1" },
]); // doesn't compile :)

playground

Typescript相关问答推荐

带有联合参数的静态大小数组

基于平台的重定向,Angular 为16

contextPaneTitleText类型的参数不能分配给remoteconfig类型的关键参数'""'''

带占位符的模板文字类型何时扩展另一个?

不推荐使用Marker类-Google map API警告

如何将CSV/TXT文件导入到服务中?

是否可以判断某个类型是否已合并为内部类型?

APP_INITIALIZER在继续到其他Provider 之前未解析promise

基元类型按引用传递的解决方法

在打字脚本中对可迭代对象进行可变压缩

如何使用TypeScrip在EXPO路由链路组件中使用动态路由?

TypeScrip:如何缩小具有联合类型属性的对象

将类型脚本函数返回类型提取到VSCode中的接口

完全在类型系统中构建的东西意味着什么?

窄SomeType与SomeType[]

如何将元素推入对象中的类型化数组

使用 fp-ts 时如何使用 TypeScript 序列化任务执行?

我是否应该在 Next.js 中的服务器组件中获取秘密数据的所有函数中使用使用服务器?

在Typescript 中,是否有一种方法来定义一个类型,其中该类型是字符串子集的所有可能组合?

是否有可能不允许在 TypeScript 中设置类属性,但也会抛出运行时错误?