Typescript: Correctif rapide pour 'les unions ne peuvent pas être utilisées dans les signatures d'index, utilisez plutôt un type d'objet mappé'

Créé le 17 mai 2018  ·  37Commentaires  ·  Source: microsoft/TypeScript

Le code suivant:

type K = "foo" | "bar";

interface SomeType {
    [prop: K]: any;
}

Donne ce message d'erreur:

An index signature parameter type cannot be a union type. Consider using a mapped object type instead.

Personne ne sait ce que sont les types d'objets mappés, alors donnons-leur une solution rapide

  • Bascule la signature d'index vers un type mappé
  • Déplace les autres membres vers un type d'objet distinct qui est combiné avec un type d'intersection
  • Remplace le type d'objet conteneur par un alias de type si le type d'objet conteneur est une interface
  • Intersecte toutes les clauses extends si le type d'objet contenant est une interface et a des clauses extends
Error Messages Quick Fixes Moderate Fixed Suggestion help wanted

Commentaire le plus utile

Tu peux le faire:

type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any};

Bien que Bar n'ait pas de signature d'index (c'est-à-dire que vous ne pouvez pas alors faire (obj as Bar)[value as Foo] ).

Edit: Bien que si vous pouviez faire de la mise en garde un non-problème, je vous serais éternellement reconnaissant!

Tous les 37 commentaires

Personne ne sait ce que sont les types d'objets mappés, alors donnons-leur une solution rapide

+1, je suis venu ici parce que je m'attendais à ce que 2.9 soutienne les syndicats en tant que signatures d'index selon votre exemple de code. Je pense que c'est une fonctionnalité souhaitée depuis longtemps: # 5683, # 16760, etc.

Tu peux le faire:

type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any};

Bien que Bar n'ait pas de signature d'index (c'est-à-dire que vous ne pouvez pas alors faire (obj as Bar)[value as Foo] ).

Edit: Bien que si vous pouviez faire de la mise en garde un non-problème, je vous serais éternellement reconnaissant!

j'aimerais travailler là-dessus: rire:

Déplace les autres membres vers un type d'objet distinct qui est combiné avec un type d'intersection

que devons-nous faire si le type d'objet contenant est une classe?
Je ne peux qu'imaginer que c'est une interface

Alors, que doit faire le code après quickfix?

type K = "1" | "2"

class SomeType {
    a = 1;
    [prop: K]: any;
}

Alors, que doit faire le code après quickfix?

Je dirais que cela ne devrait pas être réparable.

@mhegazy J'utilise 3.0.0-rc et j'obtiens toujours la même erreur que celle publiée à l'origine. Est-ce prévu?

J'utilise 3.0.0-rc et j'obtiens toujours la même erreur que celle publiée à l'origine. Est-ce prévu?

Oui. l'erreur est correcte. ce problème consistait à suivre l'ajout d'une solution rapide, à savoir la pâte légère à côté du message d'erreur.

aucune action de code disponible avec 2.9.1 et vscode

@ThaJay Nous ne rétroporterons pas cette fonctionnalité, essayez de configurer une version plus récente.

Évidemment. Je suis désolé de ne pas avoir vérifié la chronologie en premier, j'ai juste supposé que ce serait assez nouveau. Nouveau sur ts. Va vérifier avec la version 3.

comment décrire cela:

function createRequestTypes(base){
  return ['REQUEST', 'SUCCESS', 'FAILURE'].reduce((acc, type) => {
    acc[type] = `${base}_${type}`
    return acc
  }, {})
}

const user = createRequestTypes('USER')
console.log(user.REQUEST) // error
// just string? like:
interface IRequestType: {[key: string]: string}

J'ai essayé ci-dessous, tout a échoué:

type requestStatus = 'REQUEST' | 'SUCCESS' | 'FAILURE'
type requestTypes = {
  [key in requestStatus]: string
}
// or
interface IRequestTypes {[key: keyType]: string}
// or even
type requestTypes = {
  FAILURE: string,
  SUCCESS: string,
  REQUEST: string
}

@maicWorkGithub, c'est parti :

const user = createRequestTypes('USER')
console.log(user.REQUEST) 

function createRequestTypes(base:string):requestTypes {
  const result : requestTypes    = {}
  const arr    : requestStatus[] = ['REQUEST', 'SUCCESS', 'FAILURE']  

  return arr.reduce((acc, type) => {
    acc[type] = `${base}_${type}`
    return acc
  }, result)
}


type requestStatus = 'REQUEST' | 'SUCCESS' | 'FAILURE'
type requestTypes = { [key in requestStatus]?: string }

@ihorskyi Merci !!

Je suis simplement curieux de savoir pourquoi type fonctionne, mais pas interface . Quelqu'un peut-il expliquer, s'il vous plaît? Quelle est la raison d'une telle limitation (ou d'une fonctionnalité?) De interface .

type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any}; // ok
interface Baz {[key in Foo]: any} // =>

// A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.ts(1169)
// A computed property name must be of type 'string', 'number', 'symbol', or 'any'.ts(2464)
// 'Foo' only refers to a type, but is being used as a value here.ts(2693)

C'était une solution automatique incroyable à découvrir. Merci de l'avoir implémenté! :)

Idem pour les cours.

Tu peux le faire:

type Foo = 'a' | 'b';
type Bar = {[key in Foo]: any};

Bien que Bar n'ait pas de signature d'index (c'est-à-dire que vous ne pouvez pas alors faire (obj as Bar)[value as Foo] ).

Edit: Bien que si vous pouviez faire de la mise en garde un non-problème, je vous serais éternellement reconnaissant!

Utilisez plutôt Record !

type Foo = 'a' | 'b'
type Bar = Record<Foo, any>

Pour ajouter un autre exemple de ceci en utilisant une classe ...

class Foo {
   a: string;
   b: string;
}

type Bar = {[key in keyof Foo]: any};

c'est encore mieux lors de l'utilisation partielle

type A = 'x' | 'y' | 'z';
type M = Partial<{
    [key in A]: boolean
}>;

Personne ne sait ce que sont les types d'objets mappés, alors donnons-leur une solution rapide

@DanielRosenwasser
Pourquoi le message d'erreur ne peut-il pas suggérer la réponse, par exemple montrer un exemple rapide utilisant le type mappé - ce ne serait que quelques lignes de code qui seraient cohérentes avec la longueur moyenne des messages d'erreur Typescript: trollface:

est-ce que quelqu'un sait s'il est possible de dire que l'interface qui utilise le type ou enum comme clé ne peut accepter qu'une seule propriété?

Par exemple, une signature comme celle-ci: { <field>: { <and|or|xor>: <int> } } prise de l' opérateur mongo bitwise .

export enum BitwiseOperator {
    and = "and",
    or = "or",
    xor = "xor",
}

export type BitwiseCondition = {
    [key in BitwiseOperator]?: number;
}

Et puis lors de son utilisation, je voudrais valider que la variable qui est définie par l'interface, n'a qu'une seule propriété.

const query: BitwiseCondition = {
  and: 5,
  or: 6  // raise a ts error
};

@ b4dnewz Vous ne pouvez pas le faire dans Typescript. Solution de contournement: https://github.com/Microsoft/TypeScript/issues/10575

@ b4dnewz , si vous voulez seulement 1 propriété, pourquoi ne pas le faire comme ça?

export enum BitwiseOperator {
  and = "and",
  or = "or",
  xor = "xor",
}

export type BitwiseCondition = {
  operator: BitwiseOperator;
  value: number;
}

@benwinding, malheureusement, la forme retournée est différente de ce que mongodb attend

@apieceofbart merci pour la suggestion, je l'ai regardé, un peu redondant en termes d'interfaces mais peut fonctionner, je ne sais pas si je vais l'implémenter maintenant, car ce n'est pas un gros problème si l'utilisateur final essaie un condition au niveau du bit avec deux opérateurs, mongo lancera quand même une erreur

J'essaie de garder les définitions de mongo-operators aussi simples que possible pour m'éviter des maux de tête 😁 peut-être qu'à l'avenir un support approprié sera ajouté

@ b4dnewz assez bien,

Une option plus simple que vous pourriez peut-être utiliser est:

export type BitwiseCondition =
  | { or: number }
  | { xor: number }
  | { and: number }

C'est à peu près le plus proche que vous obtiendrez sans trop de duplication

@ b4dnewz assez bien,

Une option plus simple que vous pourriez peut-être utiliser est:

export type BitwiseCondition =
  | { or: number }
  | { xor: number }
  | { and: number }

C'est à peu près le plus proche que vous obtiendrez sans trop de duplication

Cela ne produira pas d'erreur dans cet exemple:

const query: BitwiseCondition = {
  and: 5,
  or: 6  // raise a ts error
};

Je pensais que c'était le but

@apieceofbart ,

Cela ne produira pas d'erreur dans cet exemple:

export type BitwiseCondition =
  | { or: number }
  | { xor: number }
  | { and: number }

const query: BitwiseCondition = {
  and: 5,
  or: 6  // doesn't raise a ts error!
};

Woah! c'est bizarre: open_mouth: Je ne savais pas ça!

Il semble que Typescript ne prend pas en charge les types mutuellement exclusifs pour les objets. C'est également une proposition pour le langage ici: https://github.com/microsoft/TypeScript/issues/14094

C'est toujours techniquement possible ...

À partir de cette réponse types conditionnels (les types les plus difficiles), mais ce n'est pas joli ...

/*
 XOR boiler plate
*/
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = T | U extends object
  ? (Without<T, U> & U) | (Without<U, T> & T)
  : T | U;
type XOR3<S, T, U> = XOR<S, XOR<T, U>>;

// Code start
export type BitwiseCondition = XOR3<
  { or: number },
  { xor: number },
  { and: number }
>;

const query1: BitwiseCondition = {
  and: 5
};

const query: BitwiseCondition = {
  and: 5,
  or: 6 // raise a ts error
};

Si quelqu'un pouvait rendre cela plus joli ou meilleur, s'il vous plaît faites

@mvasin FWIW, cela _apparaît_ pour obtenir le même résultat, mais je suis entièrement d'accord pour dire que cela devrait être une caractéristique des interfaces tout comme sur les types.

type Foo = 'a' | 'b';

type Bar = {
  [key in Foo]: any
}

interface A extends Bar { }

class Wol implements A{
  a: any;
  b: any;
}

Pour typescript 3.5, il semble que je doive faire ceci:

export interface DataTableState {
  columnStats: {[key in keyof DataTable]?:{}}
}

Est-ce la meilleure façon de procéder?

Pourquoi exactement une signature d'index ne peut-elle pas utiliser un type enum? Le type mappé fait presque ce que je veux, mais TypeScript s'attend à ce que chaque chaîne de l'énumération existe en tant que clé définie. Je ne veux pas vraiment affirmer que chaque clé existe, plus que si des clés existent, elles doivent vivre dans l'énumération.

Par exemple pour le type:

type MyType = {
  [Key: 'foo' | 'bar' | 'zip']: number;
};

Cela devrait satisfaire:

const x: MyType = {
  foo: 1,
  zip: 2
};

Bien que je puisse simplement définir les autres clés non définies pour un type mappé, je préfère rendre les clés facultatives, mais si elles sont présentes, la valeur ne peut pas être indéfinie. Si je rend les valeurs de type mappées facultatives, le code fonctionne mais les types sont moins forts.

c'est encore mieux lors de l'utilisation partielle

type A = 'x' | 'y' | 'z';
type M = Partial<{
    [key in A]: boolean
}>;

Merci!
Utile lorsque vous devez définir un type qui correspond partiellement à un dictionnaire

«Partiel» peut également être utilisé sur les enregistrements:

type Foo = 'a' | 'b';
let foo1: Record<Foo, number> = { a: 1, b: 2 };
let foo2: Partial<Record<Foo, number>> = { a: 1 };

Je me retrouve involontairement à visiter cette page GitHub tous les mois environ.

Ma dernière est très simple:

interface ObjectLiteral {
    [key: string | number]: any
}
export const mapToObjectLiteral = (map: Map<string|number, any>) =>
    Array.from(map).reduce((objLit, [key, value]) => {
        objLit[key] = value
        return objLit
    }, {} as ObjectLiteral)

image

Je peux faire défiler vers le haut et trouver une solution de contournement, mais je voulais simplement fournir des commentaires sur le fait que ce problème se produit fréquemment dans le travail quotidien dans des scénarios légèrement différents.

Voici un exemple:

type MapKey = string | number;
type ObjectLiteral<T extends MapKey, V = any> = {
  [P in T extends number ? string : T]: V;
};

export const mapToObjectLiteral = <T extends MapKey, V>(map: Map<T, V>) =>
  Array.from(map).reduce((objLit, [key, value]) => {
    objLit[key as keyof ObjectLiteral<T>] = value;
    return objLit;
  }, {} as ObjectLiteral<T, V>);

// how to make a better type of map ?
const m = new Map<1 | "foo", "a" | "b">();
m.set(1, "a");
m.set("foo", "b");

const o = mapToObjectLiteral(new Map(m));

console.log(o[1], o.foo); // just got an union type of every member of 'o'

https://github.com/microsoft/TypeScript/issues/24220#issuecomment -504285702

Pour ajouter un autre exemple de ceci en utilisant une classe ...

class Foo {
   a: string;
   b: string;
}

type Bar = {[key in keyof Foo]: any};

Très utile. Merci! 🚀

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

Antony-Jones picture Antony-Jones  ·  3Commentaires

dlaberge picture dlaberge  ·  3Commentaires

remojansen picture remojansen  ·  3Commentaires

wmaurer picture wmaurer  ·  3Commentaires

DanielRosenwasser picture DanielRosenwasser  ·  3Commentaires