Come evitare l'errore "La firma dell'indice del tipo di oggetto ha implicitamente un tipo" qualsiasi "quando si compila il dattiloscritto con flag noImplicitAny abilitato?


309

Compile sempre Typescript con la bandiera --noImplicitAny. Questo ha senso poiché voglio che il mio controllo del tipo sia il più stretto possibile.

Il mio problema è che con il seguente codice ottengo l'errore 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];

È importante notare che l'idea è che la variabile chiave provenga da qualche altra parte dell'applicazione e possa essere una qualsiasi delle chiavi nell'oggetto.

Ho provato a trasmettere esplicitamente il tipo da:

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

O il mio scenario non è proprio possibile con --noImplicitAny?

Risposte:


337

L'aggiunta di una firma indice consentirà a TypeScript di sapere quale dovrebbe essere il tipo.

Nel tuo caso sarebbe [key: string]: string;

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

Tuttavia, ciò impone anche che tutti i tipi di proprietà corrispondano alla firma dell'indice. Dal momento che tutte le proprietà sono a stringfunziona.

Mentre le firme di indice sono un modo efficace per descrivere l'array e il modello di "dizionario", impongono anche che tutte le proprietà corrispondano al loro tipo di ritorno.

Modificare:

Se i tipi non corrispondono, è possibile utilizzare un tipo di unione [key: string]: string|IOtherObject;

Con i tipi di unione, è meglio lasciare che TypeScript deduca il tipo invece di definirlo.

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

Anche se questo può diventare un po 'disordinato se ci sono molti tipi diversi nelle firme dell'indice. L'alternativa a quella è usare anynella firma. [key: string]: any;Quindi dovresti lanciare i tipi come hai fatto sopra.


E se la tua interfaccia sembra interfaccia ISomeObject {firstKey: string; secondKey: IOtherObject; } questo non è possibile immagino?
Jasper Schulte,

Grazie! La combinazione di un qualsiasi tipo insieme al cast di un tipo per caso sembra un modo venduto per andare.
Jasper Schulte,

Ciao, Come gestire "anyObject [key: Object] ['name']"?
Code_Crash,

o dì qualcosa come _obj = {}; let _dbKey = _props [chiave] ['nome']; _obj [_dbKey] = this [chiave]; qui _props è oggetto e l'oggetto [chiave] restituirà anche un oggetto che avrà la proprietà name.
Code_Crash,

180

Un altro modo per evitare l'errore è usare il cast in questo modo:

let secondValue: string = (<any>someObject)[key]; (Nota tra parentesi)

L'unico problema è che questo non è più sicuro per il tipo, come stai lanciando any. Ma puoi sempre eseguire il cast del tipo corretto.

ps: sto usando il dattiloscritto 1.7, non sono sicuro delle versioni precedenti.


20
Per evitare avvisi di tslint, puoi anche usare:let secondValue: string = (someObject as any)[key];
briosheje il

97

TypeScript 2.1 ha introdotto un modo elegante per gestire questo problema.

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

Siamo in grado di accedere a tutti i nomi delle proprietà degli oggetti durante la fase di compilazione per keyofparola chiave (vedi registro delle modifiche ).

Hai solo bisogno di sostituire il stringtipo di variabile con keyof ISomeObject. Ora il compilatore sa che la keyvariabile può contenere solo nomi di proprietà da ISomeObject.

Esempio 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];

Codice in diretta su typescriptlang.org (set noImplicitAnyopzione)

Ulteriori letture con più keyofusi .


6
Tuttavia non funzionerà se si dichiara keycome const key = (keyof ISomeObject)= 'secondo' + 'chiave'
Deluso

55

La seguente impostazione di tsconfig ti permetterà di ignorare questi errori - impostalo su true.

suppressImplicitAnyIndexErrors

Elimina gli errori NoImplicitAny per indicizzare oggetti privi di firme di indice.


14
questo è qualcosa che non dovresti fare - probabilmente qualcuno nel tuo team ha impostato esplicitamente questa opzione del compilatore per rendere il codice più a prova di proiettile!
atsu85,

12
Non sono d'accordo sul fatto che questa opzione sia stata fatta per: Consenti notazione parentesi con --noImplicitAny. Abbina perfettamente la domanda di op.
Ghetolay,

4
Sono d'accordo con @Ghetolay. Questa è anche l'unica opzione se non è possibile modificare l'interfaccia. Ad esempio, con interfacce interne come XMLHttpRequest.
Marco Roy,

1
Sono anche d'accordo con @Ghetolay. Sono curioso di sapere come questo sia qualitativamente diverso dalla risposta di Pedro Villa Verde (a parte il fatto che il codice è meno brutto). Sappiamo tutti che l'accesso a una proprietà dell'oggetto tramite una stringa dovrebbe essere evitato, se possibile, ma a volte godiamo di quella libertà pur comprendendo i rischi.
Stephen Paul,

Sono solo dei compromessi. Scegli quello che ti piace: meno area di errore e accesso all'indice rigoroso, oppure hai più area di superficie per gli errori e accedi facilmente a indici sconosciuti. L' keyofoperatore TS2.1 può aiutarti a mantenere tutto rigoroso, vedi la risposta di Piotr!
trusktr,

24

La soluzione "keyof" menzionata sopra funziona. Ma se la variabile viene utilizzata una sola volta, ad es. Eseguendo il ciclo continuo di un oggetto, ecc., Puoi anche digitarla.

for (const key in someObject) {
    sampleObject[key] = someObject[key as keyof ISomeObject];
}

Grazie. Funziona per l'accesso alle chiavi arbitrario durante l'iterazione delle chiavi di un altro oggetto.
Bucarest,

19

uso keyof typeof

const cat = {
    name: 'tuntun'
}

const key: string = 'name' 

cat[key as keyof typeof cat]

7

Simile alla risposta di @Piotr Lewandowski, ma all'interno di un forEach:

const config: MyConfig = { ... };

Object.keys(config)
  .forEach((key: keyof MyConfig) => {
    if (config[key]) {
      // ...
    }
  });

Come hai fatto a farlo funzionare? Sto cercando la stessa cosa (ts 3.8.3), anche se genera un errore dicendo: Argument of type '(field: "id" | "url" | "name") => void' is not assignable to parameter of type '(value: string, index: number, array: string[]) => void'. Il mio codice è simile a questo Object.keys(components).forEach((comp: Component) => {...}, dov'è Componentun tipo (come MyConfig).
theGirrafish

6

Dichiarare l'oggetto in questo modo.

export interface Thread {
    id:number;
    messageIds: number[];
    participants: {
        [key:number]: number
    };
}

6

Nessun indicizzatore? Quindi creane uno tuo!

L'ho definito globalmente come un modo semplice per definire la firma di un oggetto. Tpuò essere anyse necessario:

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

Aggiungo solo indexercome membro della classe.

indexer = this as unknown as Indexer<Fruit>;

Quindi finisco con questo:

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

Il vantaggio di questo è che si ottiene il tipo giusto indietro - molte soluzioni che usano <any>perderanno la digitazione per te. Ricorda che questo non esegue alcuna verifica di runtime. Dovrai comunque verificare se esiste qualcosa se non sai per certo che esiste.

Se vuoi essere eccessivamente cauto e stai usando strictpuoi farlo per rivelare tutti i luoghi che potresti aver bisogno di fare un controllo esplicito indefinito:

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

Di solito non lo trovo necessario poiché se ho una proprietà stringa da qualche parte, di solito so che è valido.

Ho trovato questo metodo particolarmente utile se ho molto codice che deve accedere all'indicizzatore e la digitazione può essere modificata in un solo posto.

Nota: sto usando la strictmodalità ed unknownè assolutamente necessario.

Il codice compilato sarà semplicemente indexer = this, quindi è molto simile a quando dattiloscritto crea _this = thisper te.


1
In alcuni casi potresti essere in grado di usare il Record<T>tipo - al momento non sono in grado di ricercare i dettagli di questo, ma per alcuni casi limitati potrebbe funzionare meglio.
Simon_Weaver,

5

Creare un'interfaccia per definire l'interfaccia 'indicizzatore'

Quindi crea il tuo oggetto con quell'indice.

Nota: questo avrà ancora gli stessi problemi che altre risposte hanno descritto riguardo al rafforzamento del tipo di ciascun elemento, ma spesso è esattamente quello che vuoi.

Puoi rendere il parametro di tipo generico qualunque sia necessario: 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];

Parco giochi dattiloscritto


Puoi persino usarlo in un vincolo generico quando definisci un tipo generico:

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

Quindi Tall'interno della classe può essere indicizzato :-)


Non credo che ci sia un'interfaccia standard 'incorporata' per Dictionaryquello che rappresenta { [key: string]: T }, ma se mai c'è per favore modifica questa domanda per rimuovere il mio ObjectIndexer.
Simon_Weaver,

3

Dichiarare il tipo quale chiave è stringa e il valore può essere qualsiasi quindi dichiarare l'oggetto con questo tipo e il lint non verrà visualizzato

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

Quindi il tuo codice sarà

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

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

    let key: string = 'secondKey';

    let secondValue: string = someObject[key];

1

Ad oggi la soluzione migliore è dichiarare i tipi. Piace

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];

1

La soluzione più semplice che ho trovato utilizzando Typescript 3.1 in 3 passaggi è:

1) Crea interfaccia

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

2) Crea una copia digitata

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

3) Utilizzare ovunque (JSX incluso)

<input customProp={copy} />
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.