2 votes

Comment typer une carte où le type de la valeur dépend du type de la clé ?

Est-il possible de taper un Map<K, V> de telle sorte que le type de la valeur dépende du type de la clé, sans spécifier le type de la clé lors de la création de la carte ?

Exemple :

abstract class BaseA { a() {} }
class ConcreteA1 extends BaseA { a1() {} }
class ConcreteA2 extends BaseA { a2() {} }
abstract class BaseB { b() {} }
class ConcreteB extends BaseB { b1() {} }

const map = new Map<???, ???>();
map.set(BaseA, ConcreteA1); // should be OK
map.set(BaseA, ConcreteA2); // should be OK
map.set(BaseB, ConcreteB);  // should be OK

map.set(BaseA, ConcreteB); // should error

Bonus : peut-on faire en sorte que ce soit aussi une erreur ?

Desde BaseA est abstrait, cela devrait être une erreur également, si possible, car BaseA ne satisfait pas new (...args: any[]) => BaseA puisqu'il n'est pas instanciable.

map.set(BaseA, BaseA); // should error as a bonus

UPDATE :

J'ai besoin de conserver une carte des implémentations de dépendance, de sorte que les valeurs soient des implémentations concrètes (extend) de la clé (type de base).

1voto

jcalz Points 30410

Le site Types de caractères TypeScript pour Map soutien uniquement Map<K, V> donde K est le type de clé, V est le type de valeur, et il n'y a pas d'autre corrélation entre eux. Ainsi, si vous utilisez simplement Map en l'état, vous ne pouvez pas faire en sorte que le compilateur TypeScript applique des contraintes supplémentaires.

D'après ma lecture de votre question, je comprends que votre contrainte est que la clé doit être un type constructeur (éventuellement abstrait), et que pour chaque clé, la valeur doit être un constructeur (nécessairement concret) d'un type compatible. Et puisque votre code d'exemple n'utilise que le set() les typages minimums pour que le code de votre exemple se comporte comme souhaité ressemblent à ceci :

interface MySpecialMap {
  set<T extends object>(
    k: abstract new (...args: any) => T, 
    v: new (...args: any) => T
  ): this;
}
interface MySpecialMapConstructor {
  new(): MySpecialMap;
}
const MySpecialMap: MySpecialMapConstructor = Map;

Ici, j'ai défini le MySpecialMap pour être juste le Map au moment de l'exécution, mais je lui ai donné le type MySpecialMapConstructor qui construit des instances de MySpecialMap . Le site MySpecialMap n'a qu'une seule méthode, set() qui est Générique dans le type T des instances de la classe de base. Le site k est un paramètre éventuellement abstract Constructeur de type T tandis que les v est un constructeur nécessairement concret de type T instances.

Démontrons que cela fonctionne comme souhaité :

abstract class BaseA { a() { } }
class ConcreteA1 extends BaseA { a1() { } }
class ConcreteA2 extends BaseA { a2() { } }
abstract class BaseB { b() { } }
class ConcreteB extends BaseB { b1() { } }

const map = new MySpecialMap();
map.set(BaseA, ConcreteA1); // ok
map.set(BaseA, ConcreteA2); // ok
map.set(BaseB, ConcreteB);  // ok
map.set(BaseA, ConcreteB); // error
// Property 'a' is missing in type 'ConcreteB' but required in type 'BaseA'
map.set(BaseA, BaseA); // error
// Cannot assign an abstract constructor type to a non-abstract constructor type.

Ça a l'air bon !

Après cela, vous voudrez probablement ajouter des typages similaires pour get() ou toute autre méthode dont vous avez besoin. Vous pouvez également modifier la signature de l'appel de la méthode set() pour spécifier des constructeurs à argument zéro ou toute autre contrainte que vous voulez faire respecter. Mais nous espérons que cela vous aidera à démarrer.

Lien du terrain de jeu vers le code

0voto

Roei Points 41

TypeScript est un système de types structurels. Un système de type structurel signifie que lors de la comparaison des types, TypeScript ne prend en compte que les membres du type. Ceci est en contraste avec les systèmes de type nominaux, où vous pouvez créer deux types mais ne pouvez pas les assigner l'un à l'autre. (Lire la suite aquí )

Pour aller au-delà, vous pouvez rendre votre classe de base unique (par exemple, en ajoutant un membre privé #type ). En utilisant cela, vous pouvez créer un nouveau type qui n'autoriserait que certaines cartes à exister. Voici un exemple :

abstract class BaseA {
    #type = BaseA.name;
}
class ConcreteA extends BaseA {
}
abstract class BaseB {}
class ConcreteB extends BaseB {
}

type MyMap<T, S> = S extends T ? Map<T, S> : never; 
const map1: MyMap<BaseA, ConcreteA> = new Map();
//    ^?
const map2: MyMap<BaseA, ConcreteB> = new Map();
//    ^?

Lien vers le terrain de jeux

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