8 votes

Combinaison des génériques avec le type d'index

Puisque ça marche :

const f = <T extends string>(x: T) => x;
f("");

interface Dictionary<T> { [key: string]: T; }
const dict: Dictionary<number> = { a: 1 };

Je m'attendais à ce que le code suivant fonctionne également :

interface MyRecord<Key extends string, Value> { [_: Key]: Value };

mais les rapports du compilateur sur _ :

An index signature parameter type must be 'string' or 'number'.

Changer Key extends string a Key extends string | number ne fait rien (même erreur).

Quelle est la raison de cet échec et comment trouver une solution correcte ? (De préférence sans utiliser Any et similaires).

Edit1 :

type XY = 'x' | 'y';
const myXY: XY = 'x';
const myString: string = myXY;

Puisque cela fonctionne, je supposais qu'il en était de même avec les types indexés (sous-ensemble de string peut poser dans un rôle de string qui est requis par le type indexé).

13voto

jcalz Points 30410

Parlons de types de signature d'index y types cartographiés . Ils ont une syntaxe similaire et font des choses similaires, mais ce ne sont pas les mêmes. Voici les similitudes :

  • Il s'agit de deux types d'objets représentant une série de propriétés.

  • Syntaxe : les signatures d'index et les types mappés utilisent tous deux une notation de type clé entre crochets à l'intérieur d'un type d'objet, comme dans le cas suivant {[**_Some Key-like Expression_**]: T}

Maintenant, les différences :

SIGNATURES D'INDEX

Les signatures d'index décrivent partie d'un type d'objet ou d'une interface représentant un nombre arbitraire de propriétés du même type avec les clés d'un certain type de clé. Actuellement, il n'y a que deux choix pour ce type de clé : string ou number .

  • Syntaxe : La syntaxe d'une signature d'index se présente comme suit :

    type StringIndex<T> = {[dummyKeyName: string]: T}
    type NumberIndex<T> = {[dummyKeyName: number]: T} 

    Il existe un nom de clé fictif ( dummyKeyName ci-dessus) qui peut être ce que vous voulez et n'a pas de signification en dehors des crochets, suivi d'une annotation de type ( : ) soit string ou number .

  • Partie d'un type d'objet : une signature d'index peut apparaître à côté d'autres propriétés dans un type d'objet ou une interface :

    interface Foo {
      a: "a",
    }
  • Nombre arbitraire de propriétés : un objet de type indexable n'est pas obligé d'avoir une propriété pour chaque clé possible (ce qui n'est même pas vraiment possible pour les string ou number à part Proxy objets). Au lieu de cela, vous pouvez affecter un objet contenant un nombre arbitraire de ces propriétés à un type indexable. Notez que lorsque vous lisez une propriété d'un type indexable, le compilateur suppose que la propriété est présente (par opposition à undefined ), même avec --strictNullChecks activé, même si ce n'est pas strictement sûr du point de vue du type . Exemple :

    type StringDict = { [k: string]: string };
    const a: StringDict = {}; // no properties, okay
    const b: StringDict = { foo: "x", bar: "y", baz: "z" }; // three properties, okay
    const c: StringDict = { bad: 1, okay: "1" }; // error, number not assignable to boolean
    
    const val = a.randomPropName; // string
    console.log(val.toUpperCase()); // no compiler warning, yet
    // "TypeError: val is undefined" at runtime
  • Propriétés du même type : toutes les propriétés d'une signature d'index doivent être du même type ; le type ne peut pas être une fonction de la clé spécifique. Ainsi, "un objet dont les valeurs des propriétés sont les mêmes que celles de leurs clés" ne peut pas être représenté par une signature d'index comme quelque chose de plus spécifique que {[k: string]: string} . Si vous voulez un type qui accepte {a: "a"} mais rejette {b: "c"} vous ne pouvez pas le faire avec une signature d'index.

  • Seulement string ou number est autorisé comme type de clé : actuellement, vous pouvez utiliser une clé de type string pour représenter un type de dictionnaire, ou une signature d'indice number signature d'index pour représenter un type de tableau. Et c'est tout. Vous ne pouvez pas élargir le type à string | number ou la limiter à un ensemble particulier de string ou number comme "a"|"b" ou 1|2 . (Votre raisonnement sur la raison pour laquelle il devrait accepter un ensemble plus étroit est plausible, mais ce n'est pas ainsi que cela fonctionne. La règle est "un type de paramètre de signature d'index doit être string ou number ".) Il y avait des travaux en cours pour relaxer cette restriction et permettre des types de clés arbitraires dans une signature d'index, voir microsoft/TypeScript#26797 mais semble être bloqué du moins pour l'instant.

TYPES MAPPÉS

Un type cartographié, quant à lui, décrit un type d'objet entier, et non une interface, représentant un ensemble particulier de propriétés de types éventuellement différents avec les clés d'un certain type de clé. Vous pouvez utiliser n'importe quel type de clé pour cela bien qu'une union de littéraux soit la plus courante (si vous utilisez l'option string ou number alors cette partie du type mappé se transforme en... devinez quoi ? une signature d'index !) Dans ce qui suit, j'utiliserai uniquement une union de littéraux comme ensemble de clés.

  • Syntaxe : La syntaxe d'un type mappé ressemble à ceci :

    type Mapped<K extends keyof any> = {[P in K]: SomeTypeFunction<P>};
    type SomeTypeFunction<P extends keyof any> = [P]; // whatever

    Un nouveau type de variable P est introduit, qui itère sur chaque membre de l'union de clés in le jeu de clés K . La nouvelle variable de type est toujours présente dans la valeur de la propriété. SomeTypeFunction<P> même s'il est en dehors des crochets.

  • Un type d'objet entier : un type mappé est le type d'objet entier. Il ne peut pas apparaître à côté d'autres propriétés et ne peut pas apparaître dans une interface. C'est comme un type d'union ou d'intersection de cette manière :

    interface Nope {
        [K in "x"]: K;  // errors, can't appear in interface
    }
    type AlsoNope = {
        a: string,
        [K in "x"]: K; // errors, can't appear alongside other properties
    }
  • Un ensemble particulier de propriétés : contrairement aux signatures d'index, un type mappé doit avoir exactement une propriété par clé dans l'ensemble de clés. (Une exception à cela est si la propriété est optionnelle, soit parce qu'elle est mappée à partir d'un type avec des propriétés optionnelles, soit parce que vous modifiez la propriété pour qu'elle soit optionnelle avec l'attribut ? modificateur) :

    type StringMap = { [K in "foo" | "bar" | "baz"]: string };
    const d: StringMap = { foo: "x", bar: "y", baz: "z" }; // okay
    const e: StringMap = { foo: "x" }; // error, missing props
    const f: StringMap = { foo: "x", bar: "y", baz: "z", qux: "w" }; // error, excess props
  • Les types de propriétés peuvent varier : comme le paramètre de type de clé itérative est en portée dans le type de propriété, vous pouvez faire varier le type de propriété en fonction de la clé, comme ceci :

    type SameName = { [K in "foo" | "bar" | "baz"]: K };
    /* type SameName = {
        foo: "foo";
        bar: "bar";
        baz: "baz";
    } */
  • Tout jeu de clés peut être utilisé : vous n'êtes pas limité à string ou number . Vous pouvez utiliser n'importe quel ensemble de string ou number et même symbol (mais comme elles sont encore plus compliquées à utiliser, je ne m'en occupe pas). Vous pouvez également utiliser string ou number là-dedans, mais vous obtenez immédiatement une signature d'index lorsque cela se produit :

    type AlsoSameName = { [K in "a" | 1]: K };    
    /* type AlsoSameName = {
        a: "a";
        1: 1;   
    } */
    const x: AlsoSameName = { "1": 1, a: "a" }
    
    type BackToIndex = { [K in string]: K }
    /* type BackToIndex = {
    }*/
    const y: BackToIndex = { a: "b" }; // see, widened to string -> string

    Et comme tout jeu de clés peut être utilisé, il peut être générique :

    type MyRecord<Key extends string, Value> = { [P in Key]: Value };

Donc c'est comme ça que vous feriez MyRecord . Il ne peut pas être un type indexable, mais seulement un type mappé. Et notez que l'intégré Record<K, T> type d'utilité est essentiellement le même (il permet K extends string | number | symbol ), vous pouvez donc l'utiliser à la place de la vôtre.

Ok, j'espère que cela vous aidera ; bonne chance !

Lien vers le code

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X