Get Type of Union By Discriminant

前端 未结 1 1968
我在风中等你
我在风中等你 2021-01-06 19:14

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

相关标签:
1条回答
  • 2021-01-06 19:40

    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]
    

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

    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
    

    And then, with your original definitions:

    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
    }
    

    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!

    0 讨论(0)
提交回复
热议问题