literal de plantilla mecanografiada como clave de interfaz

CorePress2024-01-24  12

Digamos que quiero crear un objeto que contenga varios elementos mecanografiados como se muestra a continuación:

const obj: Items = {
  item1: 'foo',
  item2: 'bar',
  item3: 'baz',
}

¿Cómo debo declarar mi tipo de artículos para que sea compatible con cualquier cantidad de artículos? Probé lo siguiente con literales de plantilla de Typecript 4.1 y no parece funcionar:

interface Items {
  [P: `array${number}`]: any;
}

¿Es posible declarar un tipo como este?



------------------------------------

ACTUALIZACIÓN PARA TS4.4+

TypeScript 4.4 admitirá firmas de índice que incluyan literales de plantilla de patrón, tal como se implementa en microsoft/TypeScript#44512. Luego podrás declarar Artículos como un tipo específico, como este:

interface Items {
  [key: `item${number}`]: any;
}

Y puedes veriComprueba que funciona como deseas:

const obj: Items = {
  item1: 'foo',
  item2: 'bar',
  item2021: 'baz',
  item3: 'qux',
};

const objBad: Items = {
  item1: 'foo',
  item2: 'bar',
  itemMMXXI: 'baz', // error!
  //  ~~~~~~~~~ <--
  //  Object literal may only specify known properties,
  //  and 'itemMMXXI' does not exist in type 'Items'
  item3: 'qux'
};

Enlace al código del patio de recreo

RESPUESTA PARA TS4.1-4.3

Los literales de plantilla de patrón con el formato `item${number}` (tal como se implementó en microsoft/TypeScript#40598) no están permitidos actualmente como tipos de clave, a partir de TypeScript 4.1.

Por ahora no existe ningún tipo específico que corresponda al tipo de artículo que desea. En su lugar, podría representarlo como una restricción en un tipo y escribir una función auxiliar asItems() que solo aceptará entradas que cumplan con la restricción:

const asItems = <K extends PropertyKey>(
    obj: { [P in K]: P extends `item${number}` ? any : never }
) => obj;

Se comprobará si cada clave del obj pasado es asignable a `item${number}`. Si es así, el tipo de propiedad es cualquiera, y si no, el tipo de propiedad es nunca. Eso tenderá a causar errores en cualquier propiedad infractora:

const obj = asItems({
    item1: 'foo',
    item2: 'bar',
    item2021: 'baz',
    item3: 'qux',
}); // okay

const objBad = asItems({
    item1: 'foo',
    item2: 'bar',
    itemMMXXI: 'baz', // error!
//  ~~~~~~~~~ <-- Type 'string' is not assignable to type 'never'
    item3: 'qux'
});

Enlace al código del patio de recreo

5

1

Parece que las firmas arbitrarias no se agregarán hasta la versión 4.4. 😢

- Jordania

12/04/2021 a las 20:50

1

Recién salido de las relaciones públicas de prensa, muy cerca del cierre. @jcalz, ¿puedes actualizar el hilo cuando se publique? github.com/microsoft/TypeScript/pull/44512

- Ka Mok

18 de junio de 2021 a las 14:18

@KaMok, el PR se fusionó ayer y ahora está en la compilación nocturna, así que actualicé esta respuesta.

-jcalz

22 de junio de 2021 a las 13:36

Cómo hacer [clave: artículo${número}]: cualquiera; ¿opcional? Agregar un signo de interrogación a la clave no funciona como se esperaba.

- Joel

30 de octubre de 2023 a las 12:49

No hay ningún motivo para que sea opcional; no es necesario que incluya todas las claves posibles ni ninguna clave. ¿Qué está tratando de lograr? En realidad, los comentarios sobre respuestas antiguas no son un buen lugar para hacer nuevas preguntas, por lo que si necesitas más ayuda, quizás quieras publicar una nueva pregunta.

-jcalz

30/10/2023 a las 13:05



------------------------------------

Terminé con un enfoque que primero construye una tupla de una longitud fija (ya que sus claves no pueden be infinitamente largo), y luego itera a través de ellos mientras filtra las claves no numéricas, y lo usa para construir el tipo Elementos. Sin embargo, una advertencia, como se indicó anteriormente, es que el número no puede exceder un límite (que resulta ser 44); sin embargo, fue suficiente para mi caso de uso, así que estoy bastante satisfecho.

// https://github.com/Microsoft/TypeScript/issues/26223#issuecomment-513116547
type PushFront<TailT extends any[], FrontT> = ((...args: [FrontT, ...TailT]) => any) extends (...tuple: infer TupleT) => any ? TupleT : never;
type Tuple<ElementT, LengthT extends number, OutputT extends any[] = []> = {
    0: OutputT;
    1: Tuple<ElementT, LengthT, PushFront<OutputT, ElementT>>;
}[OutputT['length'] extends LengthT ? 0 : 1];
const N = 44;
// N larger than 44 seems to exceed recursion limit
// const N = 45;
type NameN<Name extends string, T> = {
    [P in keyof Tuple<any, typeof N> as P extends `${number}` ? `${Name}${P}` : never]: T;
};

type Items = NameN<'item', any>

Enlace al parque infantil

1

1

Puedes construir tuplas más largas sin alcanzar los límites de recursividad, mira esta respuesta si es importante

-jcalz

26 de mayo de 2021 a las 14:53



------------------------------------

Si tiene un número máximo razonable de propiedades que puede aceptar, es posible que desee que el compilador calcule estas propiedades por usted. Por ejemplo, lo siguiente generará del artículo 1 al artículo 249:

type Foo = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 10
type Bar = [...Foo, ...Foo, ...Foo, ...Foo, ...Foo]; // 50
type Baz = [...Bar, ...Bar, ...Bar, ...Bar, ...Bar]; // 250
type ItemNames = `item${Exclude<keyof Baz, '0' | keyof any[]>}`
// item1 through item249
type ItemProps = { [K in ItemNames]?: any };
interface Items extends ItemProps {
}

const obj: Items = {
    item1: 'foo',
    item2: 'bar',
    item3: 'baz',
} // okay


const objBad: Items = {
    item1: 'foo',
    item2: 'bar',
    itemMMXXI: 'baz', // error!
    //~~~~~~~~~~~~~ <-- Object literal may only specify known properties, and 'itemMMXXI'
    // does not exist in type Items
};

Puedes escalar esto hasta máximos inferiores a aproximadamente 10 000 antes de que el compilador comience a quejarse:

type Foo = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 10
type Bar = [...Foo, ...Foo, ...Foo, ...Foo, ...Foo]; // 50
type Baz = [...Bar, ...Bar, ...Bar, ...Bar, ...Bar]; // 250
type Qux = [...Baz, ...Baz, ...Baz, ...Baz, ...Baz]; // 1250
type Quux = [...Qux, ...Qux, ...Qux, ...Qux, ...Qux]; // 6250
// type Suux = [...Quux, ...Quux]; // error: 
//   Type produces a tuple type that is too large to represent.

type ItemNames = `item${Exclude<keyof Quux, '0' | keyof any[]>}`
// item1 through item6249

Enlace al código del patio de recreo



------------------------------------

Ver https://basarat.gitbook.io/typescript/type-ssistema/firmas-índice

Estás cerca

interface MyInterface {
   [key: string]: string;
}

const x: MyInterface = {
   item1: "string"
}

1

Esto no sigue la restricción de que debe ser elemento<número>.

-0xLogN

26 de febrero de 2021 a las 4:14



------------------------------------

No es posible declarar un tipo exactamente así. La respuesta de Danie A es lo más cercano que realmente puedes llegar, pero luego aceptarás todas las condiciones. Si sabes que hay una mejoraSi no sabes cuántos números necesitas y estás dispuesto a escribirlos todos, puedes hacer algo como esto:

type n = '1' | '2' | '3' | '4' | '5'; // you get the idea
type key = `item${n}`;

Estoy seguro de que ya sabías que podías hacerlo.



------------------------------------

Aquí está mi solución:

type Item<n extends number> = `item${n}`
type Items<n extends number[]> = {
    [key in Item<n[number]>]: any
}

const goodObj: Items<[1, 2, 2021, 3]> = {
    item1: 'foo',
    item2: 'bar',
    item2021: 'baz',
    item3: 'qux', 
}; // okay

const badObj: Items<[1, 2, 2021, 3]> = {
    item1: 'foo',
    item2: 'bar',
    itemMMXXI: 'baz', // error!
    //  ~~~~~~~~~ <-- Type '{ item1: string; item2: string; itemMMXXI: string; item3: string; }' is not assignable to type 'Items<[1, 2, 2021, 3]>'.
    //                Object literal may only specify known properties, and 'itemMMXXI' does not exist in type 'Items<[1, 2, 2021, 3]>'
    item3: 'qux'
};

Compartir Seguir respondido

11 de mayo de 2021 a las 14:15

usuario15316261

usuario15316261

7

1

1 insignia de bronce

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare