web-dev-qa-db-fra.com

Typescript Generics Keyof ne correspond pas à TYPE

J'ai cette interface, qui stocke simplement une clé d'une autre interface (modelKey) et la valeur de cette touche (value):

interface ValueHolder<T, H extends keyof T> {
  modelKey: H;
  value: T[H];
}

Maintenant, je veux stocker le modèle horsePower à partir du modèle suivant, avec le type correspondant dans un ValueHolder:

interface Car {
  id: number;
  horsePower?: number;
  date: Date;
};

Cela ressemble à ceci:

const test: ValueHolder<Car, keyof Car> = {
  modelKey: 'horsePower',
  value: 1000,
};

À ce stade, aucune erreur ne se produit et elle stockerait la valeur bien. Mais vous pouvez également passer une valeur de type Date:

const test: ValueHolder<Car, keyof Car> = {
  modelKey: 'horsePower',
  value: new Date(),
};

Parce que pour une raison quelconque la valeur peut accepter tous les types de clé du modèle fourni:

(property) ValueHolder<Car, keyof Car>.value: string | number | Date | undefined


Comment puis-je créer la clé value de l'interface ValueHolder n'accepte que des valeurs de type undefined | number, si vous fournissez le modelKey _ _ horsePower?

démo

2
dewey

ValueHolder Argument générique H permet à toutes les clés qui conduisent à toutes les valeurs.

Vous devez modifier légèrement votre type d'utilité principal pour rendre l'état illégal non représentable.

Considérez cet exemple:

type Values<T> = T[keyof T]

type ValueHolder<T> = Values<{
  [Prop in keyof T]: {
    modelKey: Prop;
    value: T[Prop]
  }
}>

// type Test = {
//     modelKey: "id";
//     value: number;
// } | {
//     modelKey: "horsePower";
//     value: number | undefined;
// } | {
//     modelKey: "date";
//     value: Date;
// } | undefined
type Test = ValueHolder<Car>

interface Car {
  id: number;
  horsePower?: number;
  date: Date;
};

// ok
const test: ValueHolder<Car> = {
  modelKey: 'horsePower',
  value: 1000,
};

// error 
const test2: ValueHolder<Car> = {
  modelKey: 'horsePower',
  value: new Date(),
};

terrain de je

Voir type Test. ValueHolder crée une union de toutes les valeurs autorisées. Il iTère à travers chaque clé et crée cette interface {Prop: {Modelkey: p, valeur: t [p]}}. Alors Values obtient chaque valeur d'objet {modelKey:P, value:T[P]} et fait une union d'entre eux.

[~ # ~ ~] Mise à jour [~ # ~]

Merci, fonctionne bien! Et si je veux ça T[Prop] ne peut être que de la chaîne de type? Est-ce aussi possible?

Oui c'est le cas. Il y a deux façons de le réaliser. Vous pouvez autoriser ValueHolder uniquement pour recevoir des objets où les valeurs sont des chaînes.

type ValueHolder<T extends Record<string, string>> = Values<{
  [Prop in keyof T]: {
    modelKey: Prop;
    value: T[Prop]
  }
}>

Ou, vous pouvez vérifier à l'intérieur de l'itération si T[Prop] est une chaîne ou non.

type ValueHolder<T> = Values<{
  [Prop in keyof T]: T[Prop] extends string ? {
    modelKey: Prop;
    value: T[Prop]
  } : never
}>
2
captain-yossarian