J'utilise des objets statiques pour décrire les définitions de contrat et j'essaie de générer des types basés sur eux. Ces contrats peuvent éventuellement avoir des options, que je souhaite décrire à l'aide d'un tableau de noms d'options. Je souhaite ensuite utiliser ce tableau pour construire un type permettant de transmettre des valeurs d'option. Toutes les options doivent être obligatoires. Voir le code suivant :
const definition = { optionNames: ['a', 'b'] as const }
type OptionNames = typeof definition['optionNames'][number]
// type OptionNames = "a" | "b"
type Options = Record<OptionNames, string>
// type Options = {
// a: string;
// b: string;
// }
Jusqu'ici tout va bien, je peux construire le type Options de manière statique sans aucun problème. Cependant, lorsque j'essaie de faire la même chose avec des types génériques, j'obtiens des résultats inattendus :
interface Definition { optionNames?: readonly string[] }
type ConstructOptions<T1 extends Definition, T2 = T1['optionNames'][number]> = T2 extends string | number | symbol? Record<T2, string>: {}
type GenericOptions = ConstructOptions<typeof definition>
// type GenericOptions = Record<"a", string> | Record<"b", string>
Je m'attendrais à ce que GenericOptions soit identique à Options, mais le résultat est une union d'enregistrements qui n'ont chacun qu'une des options comme clé requise. Pourquoi cela arrive-t-il?
Solution du problème
Utilisez un tuple pour vous assurer que chaque constituant n'est pas considéré séparément :
type ConstructOptions<T1 extends Definition, T2 = T1['optionNames'][number]> = [T2] extends [string | number | symbol]? Record<T2, string>: {}
Exemple complet :
const definition = { optionNames: ['a', 'b'] as const }
interface Definition { optionNames?: readonly string[] }
type ConstructOptions<T1 extends Definition, T2 = T1['optionNames'][number]> = [T2] extends [string | number | symbol]? Record<T2, string>: {}
type GenericOptions = ConstructOptions<typeof definition>;
Une chose étrange à laquelle vous n'auriez probablement jamais pensé, mais ça marche. Juste des choses TypeScript ici ✌️.
Terrain de jeux
Au fait, vous voudrez peut-être corriger cette erreur avec NonNullable
ou Required
;)
Aucun commentaire:
Enregistrer un commentaire