为什么o1型和o2型的计算方式不同?

type MyUnion = 'foo' | 'bar' | 'baz';

type GenericData = { whatever: 'whatever' };
type FooData = { foo: 'foo' };
type BarData = { bar: 'bar' };
type BazData = { baz: 'baz' };

type MyData = {
    foo: FooData;
    bar: BarData;
    baz: BazData;
}

const builders = {
  foo: (_data: GenericData) => ({} as FooData),
  bar: (_data: GenericData) => ({} as BarData),
  baz: (_data: GenericData) => ({} as BazData),
};

type Returns = {
  [Prop in keyof typeof builders]: ReturnType<typeof builders[Prop]>;
};

type Builders = {
  [Prop in MyUnion]: (_data: GenericData) => Returns[Prop];
};

type Builders2 = {
  [Prop in MyUnion]: (typeof builders)[Prop];
};

declare const d: GenericData;

function fn<T extends MyUnion>() {
  const b1 = {} as Builders[T];
  const b2 = {} as Builders2[T];

  const o1 = b1(d);
      // ^? const o1: Returns[T]
  const o2 = b2(d);
      // ^? const o2: FooData | BarData | BazData
}

Playground

推荐答案

在打字本4.6之前,o1o2都是union type FooData | BarData | BazData.不支持indexing intoamapped type(如Builders)和generic index(如K extends MyUnion),也不支持我们可以调用的单一函数类型.相反,只要您try 调用该函数,索引就会扩大到constraint(因此K变成MyUnion),然后您就会得到联合返回类型,而不是泛型返回类型.大多数try 对不同但相关类型的函数的映射进行建模的try 都会失败.您希望函数返回类型与输入泛型类型相关,但这不会发生.

TypeScrip4.6引入了一些在microsoft/TypeScript#47109中实现的修复,以帮助处理这种关联.这是专门用来处理microsoft/TypeScript#30581中描述的correlated union types的.我不会深入讨论这个问题,但它是相关的.

其中一个已实现的修复允许编译器"看到"相关性,只要您要索引的映射类型是显式编写的,其中属性是单个函数类型.这是Builders的情况,它是{[K in MyUnion]: (data: GenericData) => Returns[K]}, so Builders[K]is now transformed to(data:GenericData)=>;Returns[K], and then everything proceeds nicely, where you can call the function and get Returns[K]`.

但对于Builders2人,也就是{[K in MyUnion]: (typeof builders)[K]}人来说,情况并非如此.编译器可以将Builders2[K]转换为(typeof builders[K]),但builders不会显式编写为超过MyUnion的映射类型.它是一个单一的对象类型,有三个属性,每个属性看起来都很相似,格式为(data: GenericData) => ???.但是编译器不能将其作为单个generic函数调用类型来"看到";它最多只能将它们合并为一个联合,然后得到(data: GenericData) => FooData | BarData | BazData.哎呀.

这就是不同之处.


请注意,这与typeof几乎没有关系,您可以使用typeof编写一个"好"的版本:

const returns: Returns = {
  foo: { foo: "foo" },
  bar: { bar: "bar" },
  baz: { baz: "baz" }
}
type Builders3 = { [K in MyUnion]:
  (data: GenericData) => (typeof returns)[K]
}
function fn<K extends MyUnion>() {
  const b3 = {} as Builders3[K]
  const o3 = b3(d);
  //    ^?const o3: Returns[K]
}

而没有它的"坏"版本:

interface _Builders4 {
  foo(data: GenericData): FooData;
  bar(data: GenericData): BarData;
  baz(data: GenericData): BazData;
}
type Builders4 = { [K in MyUnion]: _Builders4[K] }

function fn<K extends MyUnion>() {    
  const b4 = {} as Builders4[K]
  const o4 = b4(d);
  //    ^?const o4: FooData | BarData | BazData
}

Playground link to code

Typescript相关问答推荐

Typescript如何从字符串中提取多个文本

在TypScript手册中可以视为接口类型是什么意思?

TypeScript将键传递给新对象错误

如何创建泛型类型组件来使用React Native的FlatList或SectionList?'

异步动态生成Playwright测试,显示未找到测试

如何在TypeScrip中从字符串联合类型中省略%1值

如何使用类型谓词将类型限制为不可赋值的类型?

我可以以这样一种方式键入对象,只允许它的键作为它的值吗?

错误TS2403:后续变量声明必须具有相同的类型.变量';CRYPTO';的类型必须是';CRYPATO';,但这里的类型是';CRYPATO';

具有自引用属性的接口

为什么我的动画状态在组件实例之间共享?

vue-tsc失败,错误引用项目可能无法禁用vue项目上的emit

有没有一种方法可以确保类型的成员实际存在于TypeScript中的对象中?

编译TypeScrip项目的一部分导致错误

如何创建由空格连接的多个字符串的类型

错误:NG02200:找不到类型为';对象';的其他支持对象';[对象对象]';.例如数组

内联类型断言的工作原理类似于TypeScrip中的断言函数?

为什么类方法参数可以比接口参数窄

TypeScript是否不知道其他函数内部的类型判断?

如何让 Promise 将它调用的重载函数的类型转发给它自己的调用者?