本文介绍了通过判别式获取并列类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设存在一个 union类型 Thing,将类型FooBarBaz与具有区别属性tag的分组在一起.

Suppose there is a union type Thing grouping together types Foo, Bar and Baz with the discriminating property tag.

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

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

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

type Union = Foo | Bar | Baz

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

Now I would like to create a mapped type where I would iterate over tags of Union and use the corresponding interface in the type mapped to the tag. The question is: Is it possible to retrieve a type from a union type by its tag value?

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}`,
}

如果没有,还有其他方法可以实现这种映射吗?

If not, is there any other way to achieve this kind of mapping?

推荐答案

在TypeScript v2.7和更早的版本中,没有编程的方式可以做到这一点.使用TypeScript以编程方式构建联合要比检查它们容易.因此,您可以改为:

In TypeScript v2.7 and earlier, there is no programmatic way to do this. It is easier to have TypeScript build unions programmatically than it is to inspect them. Therefore, you could do this instead:

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]

现在您可以像以前一样使用Union,但是各个联合组成部分可以称为Union<'Foo'>Union<'Bar'>Union<'Baz'>.为了方便起见,您仍然可以给他们起个名字:

Now you can use Union as you did before, but the individual union constituents can be referred to as Union<'Foo'>, Union<'Bar'>, and Union<'Baz'>. For convenience you can still give them names:

interface Foo extends Union<'Foo'> {}
interface Bar extends Union<'Bar'> {}
interface Baz extends Union<'Baz'> {}

并像这样键入您的函数:

And type your function like this:

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}`,
}


从TypeScript v2.8开始,将有一个名为条件类型的功能可以在类型系统中提高表现力.您可以这样编写一个通用的联合鉴别符:


Starting in TypeScript v2.8, there will be a feature called conditional types which allows a lot more expressivity in the type system. You can write a general union discriminator like this:

type DiscriminateUnion<T, K extends keyof T, V extends T[K]> = 
  T extends Record<K, V> ? T : never

然后使用原始定义:

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

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

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

type Union = Foo | Bar | Baz

您将获得几乎不可思议的效果:

You get the almost magical:

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

也可以.如果您从npm安装typescript@next,则可以立即尝试...,否则需要等待.

which also works. You can try this out now if you install typescript@next from npm... otherwise you'll need to wait.

希望有所帮助;祝你好运!

Hope that helps; good luck!

这篇关于通过判别式获取并列类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-29 20:12