Mecanografiado deriva el tipo de unión a partir de valores de tupla / matriz

94

Di que tengo una lista const list = ['a', 'b', 'c']

¿Es posible derivar de este tipo de unión de valores que es 'a' | 'b' | 'c'?

Quiero esto porque quiero definir el tipo que permite solo valores de la matriz estática, y también necesito enumerar estos valores en tiempo de ejecución, por lo que uso matriz.

Ejemplo de cómo se puede implementar con un objeto indexado:

const indexed = {a: null, b: null, c: null}
const list = Object.keys(index)
type NeededUnionType = keyof typeof indexed

Me pregunto si es posible hacerlo sin usar un mapa indexado.

EL COLOR BLANCO
fuente
Entonces, esencialmente, ¿le gustaría generar tipos sobre la marcha?
SwiftsNamesake
Los tipos solo existen al compilar, por lo que no puede crearlos dinámicamente en tiempo de ejecución.
jonrsharpe
Esta es una pregunta interesante. ¿Cuál es tu caso de uso?
unional

Respuestas:

198

ACTUALIZACIÓN Feb 2019

En TypeScript 3.4, que debería lanzarse en marzo de 2019 , será posible decirle al compilador que infiera el tipo de una tupla de literales como una tupla de literales , en lugar de, por ejemplo, string[]utilizando la as constsintaxis . Este tipo de afirmación hace que el compilador infiera el tipo más estrecho posible para un valor, incluido hacer todo readonly. Debe tener un aspecto como este:

const list = ['a', 'b', 'c'] as const; // TS3.4 syntax
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c';

Esto evitará la necesidad de una función auxiliar de cualquier tipo. ¡Buena suerte de nuevo a todos!


ACTUALIZACIÓN Julio 2018

Parece que, a partir de TypeScript 3.0, será posible que TypeScript infiera automáticamente tipos de tuplas . Una vez que se lanza, la tuple()función que necesita se puede escribir sucintamente como:

export type Lit = string | number | boolean | undefined | null | void | {};
export const tuple = <T extends Lit[]>(...args: T) => args;

Y luego puedes usarlo así:

const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'

¡Espero que funcione para la gente!


ACTUALIZACIÓN Diciembre de 2017

Desde que publiqué esta respuesta, encontré una manera de inferir tipos de tuplas si está dispuesto a agregar una función a su biblioteca. Consulte la función tuple()en tuple.ts . Al usarlo, puede escribir lo siguiente y no repetirlo:

const list = tuple('a','b','c');  // type is ['a','b','c']
type NeededUnionType = typeof list[number]; // 'a'|'b'|'c'

¡Buena suerte!


ORIGINAL Julio de 2017

Un problema es que el literal ['a','b','c']se inferirá como tipo string[], por lo que el sistema de tipos se olvidará de los valores específicos. Puede forzar al sistema de tipos a recordar cada valor como una cadena literal:

const list = ['a' as 'a','b' as 'b','c' as 'c']; // infers as ('a'|'b'|'c')[]

O, quizás mejor, interprete la lista como un tipo de tupla:

const list: ['a','b','c'] = ['a','b','c']; // tuple

Esta es una repetición molesta, pero al menos no introduce un objeto extraño en tiempo de ejecución.

Ahora puede obtener su sindicato así:

type NeededUnionType = typeof list[number];  // 'a'|'b'|'c'.

Espero que ayude.

jcalz
fuente
Esta es una excelente solución para un problema con el que me encuentro con bastante frecuencia, cuando necesito verificaciones de tiempo de ejecución (usando la tupla) y compilar verificaciones de tiempo usando una unión de tipos. ¿Alguien sabe si hay esfuerzos para agregar soporte para tupla implícita al idioma?
Jørgen Tvedt
Probé su solución pero NO funciona concont xs = ['a','b','c']; const list = tuple(...xs);
Miguel Carvajal
1
No se supone que el trabajo con la que, desde el momento en que hace const xs = ['a','b','c']el compilador ya se ha ampliado xsa string[]y completamente olvidado de los valores específicos. No puedo evitar ese comportamiento al menos a partir de TS3.2 (aunque podría haber una as constnotación futura que funcione). De todos modos, creo que la respuesta tal como está sigue siendo tan correcta como puedo hacerlo (menciono allí que ['a','b','c']se infiere string[]), así que no estoy seguro de qué más necesita.
jcalz
3
alguien puede explicar lo que [number]hace list[number]?
Orelus
3
Se analiza como (typeof list)[number]... no typeof (list[number]). El tipo T[K]es un tipo de búsqueda que obtiene el tipo de propiedad de Tcuya clave es K. En (typeof list)[number], obtienes los tipos de propiedades de (typeof list)cuyas claves son number. Las matrices typeof listtienen firmas de índice numérico , por lo que su numberclave produce la unión de todas las propiedades indexadas numéricamente.
jcalz
15

Actualización para TypeScript 3.4:

Una nueva sintaxis llamada "contextos const" que llegará a TypeScript 3.4 permitirá una solución aún más simple que no requiere una llamada de función como se demostró. Esta característica se encuentra actualmente en revisión como se ve en este PR .

En resumen, esta sintaxis permite la creación de matrices inmutables que tienen un tipo estrecho (es decir, el tipo en ['a', 'b', 'c']lugar de ('a' | 'b' | 'c')[]o string[]). De esta manera, podemos crear tipos de unión a partir de literales tan fácil como se muestra a continuación:

const MY_VALUES = <const> ['a', 'b', 'c']
type MyType = typeof MY_VALUES[number]

En sintaxis alternativa:

const MY_VALUES = ['a', 'b', 'c'] as const
type MyType = typeof MY_VALUES[number]
ggradnig
fuente
Hola, acabo de encontrar tu respuesta después de publicar esta pregunta. Te dejaré ganar algo de karma si quieres responder;) stackoverflow.com/questions/56113411/…
Sebastien Lorber
2

No es posible hacer esto con Array.

La razón es que, incluso si declara la variable como constante, el contenido de una matriz aún puede cambiar, por lo que @jonrsharpe menciona que esto es tiempo de ejecución.

Dado lo que desea, puede ser mejor usarlo interfacecon keyof:

interface X {
    a: string,
    b: string
}

type Y = keyof X  // Y: 'a' | 'b'

O bien enum:

enum X { a, b, c }
unional
fuente
¿Existe alguna forma de excluir ciertos campos? Por ejemplo, si solo quiero el acampo ..
neomib
Puedes usar Pick<T, U>para eso.
unional