Un tipo de parámetro de firma de índice no puede ser un tipo de unión. Considere usar un tipo de objeto mapeado en su lugar

102

Estoy tratando de usar el siguiente patrón:

enum Option {
  ONE = 'one',
  TWO = 'two',
  THREE = 'three'
}

interface OptionRequirement {
  someBool: boolean;
  someString: string;
}

interface OptionRequirements {
  [key: Option]: OptionRequirement;
}

Esto me parece muy sencillo, sin embargo, aparece el siguiente error:

Un tipo de parámetro de firma de índice no puede ser un tipo de unión. En su lugar, considere usar un tipo de objeto mapeado.

¿Qué estoy haciendo mal?

john maccarthy
fuente
1
El tipo de keysolo puede ser cadena, número o símbolo. enum no lo es.
unional

Respuestas:

157

Puede usar el operador TS "in" y hacer esto:

enum Options {
  ONE = 'one',
  TWO = 'two',
  THREE = 'three',
}
interface OptionRequirement {
  someBool: boolean;
  someString: string;
}
type OptionRequirements = {
  [key in Options]: OptionRequirement; // Note that "key in".
}
Nacho Justicia Ramos
fuente
5
Erm, ¿esto no se compila? El campo de juego de TypeScript dice : "Un nombre de propiedad calculado en una interfaz debe hacer referencia a una expresión cuyo tipo sea un tipo literal o un tipo de 'símbolo único'".
meriton
19
Cambiar interface OptionRequirementsatype OptionRequirements
Tyler Rick
3
esto en realidad no funciona para mí: un nombre de propiedad calculado en una interfaz debe hacer referencia a una expresión cuyo tipo es un tipo literal o un tipo de 'símbolo único'
Tyguy7
¿Podría decirnos qué versión de Typecript está utilizando?
Nacho Justicia Ramos
2
He editado esta respuesta para usar un alias de tipo asignado en lugar de una interfaz. La respuesta original no se compila con ninguna versión de TypeScript que haya visto, y ciertamente no se compila con la versión actual de TypeScript (4.0 a partir de agosto de 2020). @NachoJusticiaRamos, si pudiera demostrar que su versión original realmente funciona en algún lugar, en alguna versión de TypeScript, me complacería revertir la edición, junto con una descripción del entorno que necesita usar para que funcione. ¡Salud!
jcalz
58

La solución más sencilla es utilizar Record

type OptionRequirements = Record<Options, OptionRequirement>

También puede implementarlo usted mismo como:

type OptionRequirements = {
  [key in Options]: OptionRequirement;
}

Esta construcción solo está disponible para type, pero no interface.

El problema en su definición es decir que la clave de su interfaz debe ser de tipo Options, donde Optionses una enumeración, no una cadena, número o símbolo.

Los key in Optionsmedios "para las claves específicas que están en el tipo de unión Opciones".

typealias es más flexible y poderoso que interface.

Si no tiene que ser utilizado en la clase de su tipo, elija typemás interface.

unional
fuente
5

Tuve un problema similar, pero mi caso fue con otra propiedad de campo en la interfaz, por lo que mi solución es un ejemplo con la propiedad de campo opcional con una enumeración para las claves:

export enum ACTION_INSTANCE_KEY {
  cat = 'cat',
  dog = 'dog',
  cow = 'cow',
  book = 'book'
}

type ActionInstances = {
  [key in ACTION_INSTANCE_KEY]?: number; // cat id/dog id/cow id/ etc // <== optional
};

export interface EventAnalyticsAction extends ActionInstances { // <== need to be extended
  marker: EVENT_ANALYTIC_ACTION_TYPE; // <== if you wanna add another field to interface
}
Kurkov Igor
fuente
4

En lugar de usar una interfaz, use un tipo de objeto mapeado

enum Option {
  ONE = 'one',
  TWO = 'two',
  THREE = 'three'
}

type OptionKeys = keyof typeof Option;

interface OptionRequirement {
  someBool: boolean;
  someString: string;
}

type OptionRequirements = {                 // note type, not interface
  [key in OptionKeys]: OptionRequirement;   // key in
}
Stefan
fuente
1

En mi caso, necesitaba que las propiedades fueran opcionales, así que creé este tipo genérico.

type PartialRecord<K extends string | number | symbol, T> = { [P in K]?: T; };

Entonces úsalo como tal:

type MyTypes = 'TYPE_A' | 'TYPE_B' | 'TYPE_C';

interface IContent {
    name: string;
    age: number;
}

interface IExample {
    type: string;
    partials: PartialRecord<MyTypes, IContent>;
}

Ejemplo

const example : IExample = {
    type: 'some-type',
    partials: {
        TYPE_A : {
            name: 'name',
            age: 30
        },
        TYPE_C : {
            name: 'another name',
            age: 50
        }
    }
}
Alazzawi
fuente
0

Tuve un problema similar. Estaba tratando de usar solo claves específicas al crear validadores de forma angular.

export enum FormErrorEnum {
  unknown = 'unknown',
  customError = 'customError',
}

export type FormError = keyof typeof FormErrorEnum;

Y el uso:

static customFunction(param: number, param2: string): ValidatorFn {
  return (control: AbstractControl): { [key: FormErrorEnum]?: any } => {
    return { customError: {param, param2} };
  };
}

Esto permitirá que se utilicen de 1 a X teclas.

Interrogatorio
fuente