Series: TypeScript
Il problema Link to heading
È comune, in TypeScript, raggruppare un insieme di tipi aventi una particolare relazione in quelle che vengono definite type map, ovvero interfacce che associano ad ognuno dei tipi nell’insieme una o più chiavi per facilitarne il recupero. Un esempio per tutti è la HTMLElementTagNameMap
, la quale associa al nome di ogni elemento del DOM il tipo corrispondente:
interface HTMLElementTagNameMap {
"a": HTMLAnchorElement;
"abbr": HTMLElement;
"address": HTMLElement;
"area": HTMLAreaElement;
"article": HTMLElement;
"aside": HTMLElement;
"audio": HTMLAudioElement;
"b": HTMLElement;
...
}
Vediamo come poter eseguire l’operazione inversa: dato un tipo che sappiamo essere presente in una type map otteremo tutte le sue chiavi.
La soluzione Link to heading
Andremo a creare una type function semanticamente equivalmente alla seguente:
function InvertTypeMap(TypeMap, Type) {
type ResultingKeysUnion = never
for(Key in TypeMap) {
if(TypeMap[Key] is Type) {
ResultingKeysUnion = ResultingKeysUnion | Key
}
}
return ResultingKeysUnion
}
In essa iteriamo tutte le chiavi della TypeMap
e Key
dopo Key
valutiamo se TypeMap[Key]
è il tipo di cui vogliamo ottenere le chiavi. Solo in caso affermativo la Key
viene aggiunta alla union da restituire.
Vediamo quindi come esprimere tale funzione nel type system del linguaggio:
type InvertTypeMap<TypeMap, Type> = {
[Key in keyof TypeMap]: Type extends TypeMap[Key]
? TypeMap[Key] extends Type
? Key
: never
: never;
}[keyof TypeMap];
Utilizziamo un mapped type per iterare le chiavi di TypeMap
, e per ogni Key
controlliamo la mutua assegnabilità - che non è la migliore definizione di uguaglianza tra tipi in TypeScript, ma è sufficiente nella maggior parte dei casi - tra TypeMap[Key]
e Type
: in caso affermativo il tipo corrispondente alla Key
viene rimappato nella Key
stessa, altrimenti in never
. Andiamo infine ad eseguire il lookup con tutte le chiavi possibili, [keyof TypeMap]
, in modo tale da ottenere la union desiderata. Ricordiamo che il tipo never
è l’elemento neutro dell’unione tra tipi: per ogni tipo T
vale che T | never
è pari a T
.
Possiamo vedere InvertTypeMap
all’opera in questo playground.