Comment puis-je éviter l'erreur «La signature d'index du type d'objet a implicitement un type« tout »» lors de la compilation du script de saisie avec l'indicateur noImplicitAny activé?

309

Je compile toujours Typescript avec l'indicateur --noImplicitAny. Cela a du sens car je veux que ma vérification de type soit aussi serrée que possible.

Mon problème est qu'avec le code suivant, j'obtiens l'erreur 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];

Il est important de noter que l'idée est que la variable clé vient d'ailleurs dans l'application et peut être n'importe laquelle des clés de l'objet.

J'ai essayé de convertir explicitement le type en:

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

Ou est-ce que mon scénario n'est tout simplement pas possible avec --noImplicitAny?

Jasper Schulte
la source

Réponses:

337

L'ajout d'une signature d'index indiquera à TypeScript quel doit être le type.

Dans votre cas, ce serait [key: string]: string;

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

Cependant, cela applique également tous les types de propriétés pour correspondre à la signature d'index. Puisque toutes les propriétés sont un stringça marche.

Bien que les signatures d'index soient un moyen puissant de décrire le tableau et le modèle de «dictionnaire», elles imposent également que toutes les propriétés correspondent à leur type de retour.

Éditer:

Si les types ne correspondent pas, un type d'union peut être utilisé [key: string]: string|IOtherObject;

Avec les types d'union, il est préférable de laisser TypeScript déduire le type au lieu de le définir.

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

Bien que cela puisse devenir un peu compliqué si vous avez beaucoup de types différents dans les signatures d'index. L'alternative à cela est d'utiliser anydans la signature. [key: string]: any;Ensuite, vous devrez convertir les types comme vous l'avez fait ci-dessus.

quotrepo
la source
Et si votre interface ressemble à l'interface ISomeObject {firstKey: string; secondKey: IOtherObject; } ce n'est pas possible je suppose?
Jasper Schulte
Merci! La combinaison d'un type quelconque avec la coulée d'un type par cas semble être une solution incontournable.
Jasper Schulte
Salut, Comment gérer le "anyObject [key: Object] ['name']"?
Code_Crash
ou dites quelque chose comme _obj = {}; let _dbKey = _props [clé] ['nom']; _obj [_dbKey] = cette [clé]; ici _props est objet et objet [clé] renverra également un objet qui aura la propriété name.
Code_Crash
180

Une autre façon d'éviter l'erreur consiste à utiliser le cast comme ceci:

let secondValue: string = (<any>someObject)[key]; (Notez la parenthèse)

Le seul problème est que ce type n'est plus sûr pour le type, car vous any . Mais vous pouvez toujours restituer le type correct.

ps: J'utilise tapuscript 1.7, je ne suis pas sûr des versions précédentes.

Pedro Villa Verde
la source
20
Pour éviter les avertissements tslint, vous pouvez également utiliser:let secondValue: string = (someObject as any)[key];
briosheje
97

TypeScript 2.1 a introduit une manière élégante de gérer ce problème.

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

Nous pouvons accéder à tous les noms de propriétés d'objets pendant la phase de compilation par keyofmot-clé (voir changelog ).

Il vous suffit de remplacer le stringtype de variable par keyof ISomeObject. Le compilateur sait maintenant que la keyvariable est autorisée à contenir uniquement les noms de propriété deISomeObject .

Exemple complet:

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

Code en direct sur typescriptlang.org ( noImplicitAnyoption set )

Lectures complémentaires avec plus d' keyofusages .

Piotr Lewandowski
la source
6
Cependant, cela ne fonctionnera pas si nous déclarons keycomme const key = (keyof ISomeObject)= 'second' + 'Key'
Déçu le
55

Le paramètre tsconfig suivant vous permettra d'ignorer ces erreurs - définissez-le sur true.

suppressImplicitAnyIndexErrors

Supprimez les erreurs noImplicitAny pour l'indexation des objets sans signature d'index.

Scott Munro
la source
14
c'est quelque chose que vous ne devriez pas faire - probablement quelqu'un dans votre équipe a explicitement défini cette option de compilation pour rendre le code plus à l'épreuve des balles!
atsu85
12
Je ne suis pas d'accord que c'est exactement pour cela que cette option a été faite: Autoriser la notation entre parenthèses avec --noImplicitAny. Correspond parfaitement à la question de l'op.
Ghetolay
4
Je suis d'accord avec @Ghetolay. C'est également la seule option si la modification de l'interface n'est pas possible. Par exemple, avec des interfaces internes comme XMLHttpRequest.
Marco Roy
1
Je suis également d'accord avec @Ghetolay. Je suis curieux de voir comment cela est qualitativement différent de la réponse de Pedro Villa Verde (à part le fait que le code est moins moche). Nous savons tous que l'accès à une propriété d'objet à l'aide d'une chaîne doit être évité si possible, mais nous apprécions parfois cette liberté tout en comprenant les risques.
Stephen Paul
Ce ne sont que des compromis. Choisissez ce que vous aimez: moins de surface d'erreur et un accès aux index strict, ou avoir plus de surface pour les erreurs et accéder facilement aux index inconnus. L' keyofopérateur TS2.1 peut aider à garder tout strict, voir la réponse de Piotr!
trusktr
24

La solution «keyof» mentionnée ci-dessus fonctionne. Mais si la variable n'est utilisée qu'une seule fois, par exemple pour parcourir un objet, etc., vous pouvez également la transtyper.

for (const key in someObject) {
    sampleObject[key] = someObject[key as keyof ISomeObject];
}
Karna
la source
Merci. Cela fonctionne pour l'accès aux clés arbitraire lors de l'itération des clés d'un autre objet.
bucabay
19

utilisation keyof typeof

const cat = {
    name: 'tuntun'
}

const key: string = 'name' 

cat[key as keyof typeof cat]
alsotang
la source
7

Similaire à la réponse de @Piotr Lewandowski, mais dans un forEach:

const config: MyConfig = { ... };

Object.keys(config)
  .forEach((key: keyof MyConfig) => {
    if (config[key]) {
      // ...
    }
  });
Steve Brush
la source
Comment avez-vous réussi à faire en sorte que cela fonctionne? Je suis en train de la même chose (ts 3.8.3), bien qu'il renvoie une erreur en disant: Argument of type '(field: "id" | "url" | "name") => void' is not assignable to parameter of type '(value: string, index: number, array: string[]) => void'. Mon code ressemble à ça Object.keys(components).forEach((comp: Component) => {...}, où Componentest un type (comme MyConfig).
theGirrafish
6

Déclarez l'objet comme ceci.

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

Pas d'indexeur? Faites le vôtre!

J'ai globalement défini cela comme un moyen facile de définir une signature d'objet. Tpeut être anysi nécessaire:

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

J'ajoute juste en indexertant que membre de la classe.

indexer = this as unknown as Indexer<Fruit>;

Je me retrouve donc avec ceci:

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

L'avantage de faire cela est que vous récupérez le type approprié - de nombreuses solutions qui utilisent <any>perdront la saisie pour vous. N'oubliez pas que cela n'effectue aucune vérification d'exécution. Vous devrez toujours vérifier si quelque chose existe si vous ne savez pas avec certitude s'il existe.

Si vous voulez être trop prudent et que vous utilisez, strictvous pouvez le faire pour révéler tous les endroits dont vous pourriez avoir besoin pour effectuer une vérification explicite non définie:

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

Je ne trouve généralement pas cela nécessaire, car si j'ai une propriété de chaîne de quelque part, je sais généralement qu'elle est valide.

J'ai trouvé cette méthode particulièrement utile si j'ai beaucoup de code qui a besoin d'accéder à l'indexeur et que le typage peut être changé en un seul endroit.

Remarque: j'utilise le strictmode, et unknownc'est vraiment nécessaire.

Le code compilé le sera indexer = this, il est donc très similaire à la création de typographie _this = thispour vous.

Simon_Weaver
la source
1
Dans certains cas, vous pouvez utiliser le Record<T>type à la place - je ne suis pas en mesure pour l'instant de rechercher les détails de cela, mais pour certains cas limités, cela peut mieux fonctionner.
Simon_Weaver
5

Créer une interface pour définir l'interface «indexeur»

Créez ensuite votre objet avec cet index.

Remarque: cela aura toujours les mêmes problèmes que d'autres réponses ont décrits en ce qui concerne l'application du type de chaque élément - mais c'est souvent exactement ce que vous voulez.

Vous pouvez définir le paramètre de type générique selon vos besoins: 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];

Aire de jeux dactylographiée


Vous pouvez même l'utiliser dans une contrainte générique lors de la définition d'un type générique:

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

Ensuite, Tà l'intérieur de la classe peut être indexé :-)

Simon_Weaver
la source
Je ne pense pas qu'il existe une interface standard intégrée pour Dictionarycela { [key: string]: T }, mais s'il y en a un, veuillez modifier cette question pour supprimer ma ObjectIndexer.
Simon_Weaver
3

Déclarez le type dont sa clé est une chaîne et la valeur peut être quelconque, puis déclarez l'objet avec ce type et la peluche ne s'affichera pas

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

Votre code sera donc

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
la source
1

Aujourd'hui, la meilleure solution consiste à déclarer des types. Comme

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
la source
1

La solution la plus simple que j'ai pu trouver en utilisant Typescript 3.1 en 3 étapes est:

1) Faire l'interface

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

2) Faites une copie dactylographiée

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

3) Utilisez n'importe où (JSX inclus)

<input customProp={copy} />
Artokun
la source