Texto mecanografiado: derivar el tipo de unión de una matriz de objetos

8

Me gustaría declarar una matriz de elementos de tipo obligatorio y poder derivar un tipo de unión a partir de ella. Este patrón funciona si no proporciona explícitamente un tipo a los elementos de la matriz. No estoy seguro de cómo explicarlo mejor, así que aquí hay un ejemplo:

EJEMPLO 1

type Pair = {
  key: string;
  value: number;
};

const pairs: ReadonlyArray<Pair> = [
  { key: 'foo', value: 1 },
  { key: 'bar', value: 2 },
] as const;

type Keys = typeof pairs[number]['key']

EJEMPLO 2

type Data = {
  name: string;
  age: number;
};

const DataRecord: Record<string, Data> = {
  foo: { name: 'Mark', age: 35 },
  bar: { name: 'Jeff', age: 56 },
} as const;

type Keys = keyof typeof DataRecord;

Aquí hay un ejemplo de derivación de las claves cuando se usa as const. Quiero este mismo comportamiento pero con la matriz que se escribe explícitamente.

const pairs = [
  { key: 'foo', value: 1 },
  { key: 'bar', value: 2 },
] as const;

type Keys = typeof pairs[number]['key']; // "foo" | "bar"

valor deseado de claves: "foo"|"bar"

valor real de las claves: string

Ben
fuente
44
No creo que pueda hacer esto dinámicamente de la manera que lo está intentando, está combinando valores de tiempo de ejecución con tipos de tiempo de compilación. Tendrá que dar al keyatributo del Pairtipo el tipo que desee, entonces debería funcionar como lo ha escrito.
Jared Smith
@JaredSmith esto no debería ser un problema en tiempo de ejecución. Estoy usando esto para declarar un número arbitrario de valores que no cambian durante la ejecución. Esto sería equivalente a establecer la clave: "foo" | "bar" en la declaración de tipo.
Ben
"esto no debería ser un problema en tiempo de ejecución" --- typecript no tiene tiempo de ejecución, por lo que es un problema hacerlo en tiempo de ejecución.
zerkms
1
@Ben me permite ser más específico: no creo que pueda hacer esto con una tupla de propiedades extraídas de los tipos de referencia mutables de la misma manera que con una tupla de primitivas inmutables. Puede decir possibleKeys = ['foo', 'bar'] as const; type Keys = typeof possibleKeys[number]; type Pair = { key: Keys, value: number };pero aún necesita enumerar explícitamente las posibles claves.
Jared Smith

Respuestas:

3

Para una variable, puede dejar que el compilador deduzca el tipo de la inicialización o escribirlo explícitamente. Si lo escribe explícitamente, como lo ha hecho, entonces el valor de inicialización se compara con la anotación, pero el tipo real del inicializador no afecta el tipo de la variable (por lo que pierde la información de tipo que desea). Si deja que el compilador lo infiera, ya no es posible restringir el tipo para que se ajuste a una interfaz específica (como parece querer)

La solución para esto es usar una función genérica para restringir el valor e inferir su tipo real:

type Pair = {
  key: string;
  value: number;
};
function craetePairsArray<T extends readonly Pair[] & Array<{key: V}>, V extends string>(...args: T) {
    return args
}

const pairs = craetePairsArray(
  { key: 'foo', value: 1 },
  { key: 'bar', value: 2 },
)

type Keys1 = typeof pairs[number]['key']

type Data = {
  name: string;
  age: number;
};

function craeteDataObject<T extends Record<string, Data>>(arg: T) {
    return arg;
}
const DataRecord = craeteDataObject({
  foo: { name: 'Mark', age: 35 },
  bar: { name: 'Jeff', age: 56 },
})

type Keys2 = keyof typeof DataRecord;

Enlace de juegos

Nota: Para el caso de la matriz, necesitamos armar un poco el compilador para inferir los tipos literales de cadena para key, por lo tanto, el conjunto & Array<{key: V}>, dondeV se extiende un parámetro de tipostring

Tiziano Cernicova-Dragomir
fuente
1
¡Gracias! ¡Esto es exactamente lo que necesitaba!
Ben
2

Los enfoques habituales son:

  • deje que TS infiera el tipo de pairsomitiendo el tipo explícito ReadonlyArray<Pair>(vea la respuesta )
  • dar keyen Pairel tipo"foo"|"bar"

Si no desea hacer esto, entonces la única forma de inferir sus claves y restringir el tipo de pairses utilizar una función auxiliar. El Pairtipo también se hará genérico para guardar los keytipos literales de cadena dados . Puede usar un IIFE para hacer la asignación compacta:

type Pair<K = string> = {
    key: K;
    value: number;
};

const pairs = (<T>(p: readonly Pair<T>[]) => p)([
    { key: 'foo', value: 1 },
    { key: 'bar', value: 2 },
] as const) // readonly Pair<"foo" | "bar">[]

type Keys = typeof pairs[number]['key'] // "foo" | "bar"

Patio de recreo

bela53
fuente