typescript - Hur skapar man en unionstyp från nästlade arrayer?


Jag har ett konstant objekt som detta

const rack = {
    topShelf: {
        colors: ["red", "green"],
    },
    middleShelf: {
        colors: ["yellow", "blue"],
    },
    bottomShelf: {
        colors: ["orange", "purple"],
    },
}

Och jag vill skapa en unionstyp som:

type Color = "red" | "green" | "yellow" | "blue" | "orange" | "purple"

Rack-objektet är bara ett exempel. I en verklig applikation har objektet omkring 30 fält.

Jag vet hur man gör det manuellt

type Color = 
    | typeof rack["topShelf"]["colors"][number] 
    | typeof rack["middleShelf"]["colors"][number]
    | typeof rack["bottomShelf"]["colors"][number]

Men det är felbenäget och jag är säker på att det finns ett sätt att få denna unionstyp att härledas av Typescript.

Svar #1

Ja, det är möjligt.

const rack = {
  topShelf: {
    colors: ["red", "green"],
  },
  middleShelf: {
    colors: ["yellow", "blue"],
  },
  bottomShelf: {
    colors: ["orange", "purple"],
  },
  extraDeepNestedProperty: {
    nestedProperty: {
      colors: ['rgb']
    }
  }
} as const

type Rack = typeof rack

type IsNever<T> = [T] extends [never] ? true : false

type Union<T, Cache extends string = never> =
  IsNever<keyof T> extends true
  ? Cache
  : {
    [Prop in keyof T]:

    T[Prop] extends ReadonlyArray<string>
    ? Cache | T[Prop][number]
    : Union<T[Prop], Cache>
  }[keyof T]

type Result = Union<Rack>

Behandla Cache som en memoization.

Union är en rekursiv typ.

Lekplats Jag gjorde en generisk lösning som fungerar inte bara med 2 nivåer av nästling,

Svar #2

Jag har slutat upp med denna lösning:

const rack = {
    topShelf: {
        colors: ["red", "green"],
    },
    middleShelf: {
        colors: ["yellow", "blue"],
    },
    bottomShelf: {
        colors: ["orange", "purple"],
    },
} as const

const colors = Object.values(rack).map(shelf => shelf.colors).flat()

type Color = typeof colors[number]

Jag kommer att behöva "colors" som ett värde vid körning senare, så i mitt specifika fall är denna lösning den bästa.

Men lösningen från @captain-yossarian är generell så jag markerade den som ett svar.