¿Cómo evito el error “La firma de índice del tipo de objeto tiene implícitamente un tipo 'cualquiera'” al compilar script con la marca noImplicitAny habilitada?

309

Siempre compilo Typecript con la bandera --noImplicitAny. Esto tiene sentido ya que quiero que mi verificación de tipo sea lo más ajustada posible.

Mi problema es que con el siguiente código me sale el error Index signature of object type implicitly has an 'any' type:

interface ISomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   string;
}

let someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue'
};

let key: string = 'secondKey';

let secondValue: string = someObject[key];

Es importante tener en cuenta que la idea es que la variable clave proviene de otro lugar de la aplicación y puede ser cualquiera de las claves del objeto.

He intentado emitir explícitamente el tipo por:

let secondValue: string = <string>someObject[key];

¿O es que mi escenario simplemente no es posible --noImplicitAny?

Jasper Schulte
fuente

Respuestas:

337

Agregar una firma de índice le permitirá a TypeScript saber cuál debería ser el tipo.

En tu caso eso sería [key: string]: string;

interface ISomeObject {
    firstKey:      string;
    secondKey:     string;
    thirdKey:      string;
    [key: string]: string;
}

Sin embargo, esto también exige que todos los tipos de propiedades coincidan con la firma del índice. Dado que todas las propiedades son un stringfunciona.

Si bien las firmas de índice son una forma poderosa de describir la matriz y el patrón de 'diccionario', también imponen que todas las propiedades coincidan con su tipo de retorno.

Editar:

Si los tipos no coinciden, se puede usar un tipo de unión [key: string]: string|IOtherObject;

Con los tipos de unión, es mejor si deja que TypeScript infiera el tipo en lugar de definirlo.

// Type of `secondValue` is `string|IOtherObject`
let secondValue = someObject[key];
// Type of `foo` is `string`
let foo = secondValue + '';

Aunque eso puede ser un poco complicado si tiene muchos tipos diferentes en las firmas de índice. La alternativa a eso es usar anyen la firma. [key: string]: any;Entonces necesitarías lanzar los tipos como hiciste anteriormente.

aunquetrepo
fuente
Y si su interfaz se parece a la interfaz ISomeObject {firstKey: string; secondKey: IOtherObject; } esto no es posible, supongo?
Jasper Schulte
¡Gracias! La combinación de cualquier tipo junto con la conversión de un tipo por caso parece una manera vendida.
Jasper Schulte
Hola, ¿Cómo manejar el "anyObject [clave: Objeto] ['nombre']"?
Code_Crash
o decir algo como _obj = {}; let _dbKey = _props [clave] ['nombre']; _obj [_dbKey] = esta [clave]; aquí _props es object y object [key] también devolverá un objeto que tendrá la propiedad de nombre.
Code_Crash
180

Otra forma de evitar el error es usar el elenco de esta manera:

let secondValue: string = (<any>someObject)[key]; (Tenga en cuenta el paréntesis)

El único problema es que esto ya no es de tipo seguro, como lo está haciendo any. Pero siempre puedes volver al tipo correcto.

ps: estoy usando el mecanografiado 1.7, no estoy seguro acerca de las versiones anteriores.

Pedro Villa Verde
fuente
20
Para evitar advertencias de tslint, también puede usar:let secondValue: string = (someObject as any)[key];
briosheje
97

TypeScript 2.1 introdujo una forma elegante de manejar este problema.

const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];

Podemos acceder a todos los nombres de propiedades del objeto durante la fase de compilación por keyofpalabra clave (ver el registro de cambios ).

Solo necesita reemplazar el stringtipo de variable con keyof ISomeObject. Ahora el compilador sabe que la keyvariable solo puede contener nombres de propiedades ISomeObject.

Ejemplo completo:

interface ISomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   number;
}

const someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   3
};

const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];

// You can mix types in interface, keyof will know which types you refer to.
const keyNumber: (keyof ISomeObject) = 'thirdKey';
const numberValue: number = someObject[keyNumber];

Código en vivo en typescriptlang.org (establecer noImplicitAnyopción)

Lectura adicional con más keyofusos .

Piotr Lewandowski
fuente
66
Sin embargo, no funcionará si declaramos keycomo const key = (keyof ISomeObject)= 'segundo' + 'Clave'
Decepcionado el
55

La siguiente configuración de tsconfig le permitirá ignorar estos errores: configúrelo como verdadero.

suppressImplicitAnyIndexErrors

Suprima los errores noImplicitAny para indexar objetos que carecen de firmas de índice.

Scott Munro
fuente
14
eso es algo que no debes hacer, ¡probablemente alguien en tu equipo ha establecido explícitamente esta opción de compilación para hacer que el código sea más a prueba de balas!
atsu85
12
No estoy de acuerdo que esto es exactamente lo que esta opción se hizo para: Permitir la notación entre paréntesis con --noImplicitAny. Combina perfectamente la pregunta de op.
Ghetolay
44
Estoy de acuerdo con @Ghetolay. Esta es también la única opción si no es posible modificar la interfaz. Por ejemplo, con interfaces internas como XMLHttpRequest.
Marco Roy
1
También estoy de acuerdo con @Ghetolay. Tengo curiosidad de cómo esto es cualitativamente diferente de la respuesta de Pedro Villa Verde (aparte del hecho de que el código es menos feo). Todos sabemos que el acceso a una propiedad de objeto utilizando una cadena debe evitarse si es posible, pero a veces disfrutamos de esa libertad mientras comprendemos los riesgos.
Stephen Paul
Es solo compensaciones. Elija lo que quiera: menos área de superficie de error y acceso estricto al índice, o tenga más área de superficie para errores y acceda fácilmente a índices desconocidos. El keyofoperador TS2.1 puede ayudar a mantener todo estricto, ¡vea la respuesta de Piotr!
trusktr
24

La solución 'keyof' mencionada anteriormente funciona. Pero si la variable se usa solo una vez, por ejemplo, recorrer un objeto, etc., también puede escribirla.

for (const key in someObject) {
    sampleObject[key] = someObject[key as keyof ISomeObject];
}
Karna
fuente
Gracias. Esto funciona para el acceso a claves arbitrarias al iterar las claves de otro objeto.
bucabay
19

utilizar keyof typeof

const cat = {
    name: 'tuntun'
}

const key: string = 'name' 

cat[key as keyof typeof cat]
alsotang
fuente
7

Similar a la respuesta de @Piotr Lewandowski, pero dentro de un forEach:

const config: MyConfig = { ... };

Object.keys(config)
  .forEach((key: keyof MyConfig) => {
    if (config[key]) {
      // ...
    }
  });
Steve Brush
fuente
¿Cómo conseguiste que esto funcionara? Estoy intentando lo mismo (TS 3.8.3), aunque se lanza un error que dice: Argument of type '(field: "id" | "url" | "name") => void' is not assignable to parameter of type '(value: string, index: number, array: string[]) => void'. Mi código se ve así Object.keys(components).forEach((comp: Component) => {...}, donde Componentes un tipo (como MyConfig).
theGirrafish
6

Declara el objeto así.

export interface Thread {
    id:number;
    messageIds: number[];
    participants: {
        [key:number]: number
    };
}
Supun Dharmarathne
fuente
6

Sin indexador? ¡Entonces haz el tuyo!

Globalmente he definido esto como una manera fácil de definir una firma de objeto. Tpuede ser anysi es necesario:

type Indexer<T> = { [ key: string ]: T };

Solo agrego indexercomo miembro de la clase.

indexer = this as unknown as Indexer<Fruit>;

Así que termino con esto:

constructor(private breakpointResponsiveService: FeatureBoxBreakpointResponsiveService) {

}

apple: Fruit<string>;
pear: Fruit<string>;

// just a reference to 'this' at runtime
indexer = this as unknown as Indexer<Fruit>;

something() {

    this.indexer['apple'] = ...    // typed as Fruit

La ventaja de hacer esto es que recupera el tipo correcto: muchas de las soluciones que usa <any>perderán el tipeo por usted. Recuerde que esto no realiza ninguna verificación de tiempo de ejecución. Aún deberá comprobar si existe algo si no sabe con certeza si existe.

Si desea ser demasiado cauteloso, y lo está utilizando strict, puede hacer esto para revelar todos los lugares que puede necesitar para hacer una verificación explícita indefinida:

type OptionalIndexed<T> = { [ key: string ]: T | undefined };

Por lo general, no encuentro esto necesario ya que si tengo como propiedad de cadena de algún lugar, generalmente sé que es válido.

Este método me ha resultado especialmente útil si tengo mucho código que necesita para acceder al indexador, y la escritura se puede cambiar en un solo lugar.

Nota: Estoy usando el strictmodo, y unknowndefinitivamente es necesario.

El código compilado simplemente será indexer = this, por lo que es muy similar a cuando el mecanografiado crea _this = thispara usted.

Simon_Weaver
fuente
1
En algunos casos, es posible que pueda usar el Record<T>tipo de letra; en este momento no puedo investigar los detalles finos de esto, pero para algunos casos limitados puede funcionar mejor.
Simon_Weaver
5

Crear una interfaz para definir la interfaz 'indexador'

Luego crea tu objeto con ese índice.

Nota: esto seguirá teniendo los mismos problemas que otras respuestas han descrito con respecto a la aplicación del tipo de cada elemento, pero a menudo eso es exactamente lo que desea.

Puede hacer que el parámetro de tipo genérico sea lo que necesite: ObjectIndexer< Dog | Cat>

// this should be global somewhere, or you may already be 
// using a library that provides such a type
export interface ObjectIndexer<T> {
  [id: string]: T;
}

interface ISomeObject extends ObjectIndexer<string>
{
    firstKey:   string;
    secondKey:  string;
    thirdKey:   string;
}

let someObject: ISomeObject = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue'
};

let key: string = 'secondKey';

let secondValue: string = someObject[key];

Zona de juegos mecanografiada


Incluso puede usar esto en una restricción genérica al definir un tipo genérico:

export class SmartFormGroup<T extends IndexableObject<any>> extends FormGroup

Luego Tdentro de la clase se puede indexar :-)

Simon_Weaver
fuente
No creo que haya una interfaz estándar 'incorporada' Dictionaryque represente { [key: string]: T }, pero si la hay, edite esta pregunta para eliminar mi ObjectIndexer.
Simon_Weaver
3

Declare el tipo cuya clave es string y el valor puede ser cualquiera, luego declare el objeto con este tipo y la pelusa no aparecerá

type MyType = {[key: string]: any};

Entonces tu código será

type ISomeType = {[key: string]: any};

    let someObject: ISomeType = {
        firstKey:   'firstValue',
        secondKey:  'secondValue',
        thirdKey:   'thirdValue'
    };

    let key: string = 'secondKey';

    let secondValue: string = someObject[key];
O.AbedElBaset
fuente
1

En la actualidad, la mejor solución es declarar tipos. Me gusta

enum SomeObjectKeys {
    firstKey = 'firstKey',
    secondKey = 'secondKey',
    thirdKey = 'thirdKey',
}

let someObject: Record<SomeObjectKeys, string> = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue',
};

let key: SomeObjectKeys = 'secondKey';

let secondValue: string = someObject[key];
Artsiom Tymchanka
fuente
1

La solución más simple que pude encontrar usando el Script 3.1 en 3 pasos es:

1) Hacer interfaz

interface IOriginal {
    original: { [key: string]: any }
}

2) Hacer una copia mecanografiada

let copy: IOriginal = (original as any)[key];

3) Usar en cualquier lugar (JSX incluido)

<input customProp={copy} />
Artokun
fuente