通过判别获得联盟类型

Pet*_*dec 2 type-inference typescript typescript-typings union-types

假设有一个联合类型 Thing类型组合在一起Foo,BarBaz具有区分属性tag.

interface Foo {
  tag: 'Foo'
  foo: string
}

interface Bar {
  tag: 'Bar'
  bar: number
}

interface Baz {
  tag: 'Baz'
  baz: boolean
}

type Union = Foo | Bar | Baz
Run Code Online (Sandbox Code Playgroud)

现在我想创建一个映射类型,我将遍历标记Union并使用映射到标记的类型中的相应接口.问题是:是否可以通过其标记值从联合类型中检索类型?

interface Tagged {
  tag: string
}

type TypeToFunc<U extends Tagged> = {
  // Is it possilbe to retrieve the type for the given tag from the union type?
  // What to put in place of the ???
  readonly [T in U['tag']]: (x: ???) => string
}

const typeToFunc: TypeToFunc<Union> = {
  // x must be of type Foo
  Foo: x => `FOO: ${x.foo}`,
  // x must be of type Bar
  Bar: x => `BAR: ${x.bar}`,
  // x must be of type Baz
  Baz: x => `BAZ: ${x.baz}`,
}
Run Code Online (Sandbox Code Playgroud)

如果没有,有没有其他方法来实现这种映射?

jca*_*alz 10

在TypeScript v2.7及更早版本中,没有以编程方式执行此操作.以编程方式使用TypeScript构建联合比检查它们更容易.因此,您可以这样做:

interface UnionSchema {
  Foo: {foo: string},
  Bar: {bar: number},
  Baz: {baz: boolean}
}

type Union<K extends keyof UnionSchema = keyof UnionSchema> = {
  [P in K]: UnionSchema[K] & {tag: K}
}[K]
Run Code Online (Sandbox Code Playgroud)

现在你可以使用Union像之前,但个别工会成分可以被称为Union<'Foo'>,Union<'Bar'>Union<'Baz'>.为方便起见,您仍然可以给他们起名字:

interface Foo extends Union<'Foo'> {}
interface Bar extends Union<'Bar'> {}
interface Baz extends Union<'Baz'> {}
Run Code Online (Sandbox Code Playgroud)

然后输入你的函数:

type TypeToFunc<U extends Union> = {
  readonly [T in U['tag']]: (x: Union<T>) => string
}
const typeToFunc: TypeToFunc<Union> = {
  // x must be of type Foo
  Foo: x => `FOO: ${x.foo}`,
  // x must be of type Bar
  Bar: x => `BAR: ${x.bar}`,
  // x must be of type Baz
  Baz: x => `BAZ: ${x.baz}`,
}
Run Code Online (Sandbox Code Playgroud)

从TypeScript v2.8开始,将会有一个名为条件类型的功能,它可以在类型系统中提供更多的表现力.你可以写一个像这样的通用联合鉴别器:

type DiscriminateUnion<T, K extends keyof T, V extends T[K]> = 
  T extends Record<K, V> ? T : never
Run Code Online (Sandbox Code Playgroud)

然后,根据您的原始定义:

interface Foo {
  tag: 'Foo'
  foo: string
}

interface Bar {
  tag: 'Bar'
  bar: number
}

interface Baz {
  tag: 'Baz'
  baz: boolean
}

type Union = Foo | Bar | Baz
Run Code Online (Sandbox Code Playgroud)

你得到了几乎神奇的:

type TypeToFunc<U extends Union> = {
  readonly [T in U['tag']]: (x: DiscriminateUnion<Union,'tag',T>) => string
}
Run Code Online (Sandbox Code Playgroud)

这也有效.如果你typescript@nextnpm... 安装,你可以试试这个...否则你需要等待.


希望有所帮助; 祝好运!