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