J'ai un type Foo
qui est un Array
qui peut contenir n'importe quel nombre d'éléments Bar
, avec un dernier élément Qux
facultatif.
Voici quelques exemples de données valides:
[qux, qux] [qux, bar] [bar, bar, qux, bar] [bar, bar, qux, qux]
Exemples de données invalides:
[] [bar] [qux] [bar, qux] [bar, bar, bar, bar, bar] [bar, bar, bar, qux]
Actuellement, je l'ai comme type Foo = Array<Bar | Qux>
, mais cela ne capture pas le fait qu'un seul Qux
est autorisé, et uniquement en tant que dernier élément.
Je ne sais pas si je devrais même m'attendre à ce que Typescript soit capable d'exprimer cela, ou s'il y a un avantage pratique si cela pourrait être réalisé.
3 Réponses :
Étant donné qu'en Javascript, les paramètres rest ne sont autorisés que comme dernier paramètre, la même approche a été historiquement adoptée pour les types dans Typescript.
Il existe des tickets de longue date pour supprimer cette limitation pour les types, voir ici et ici , mais pour le moment, ce n'est toujours pas pris en charge.
Étant donné que vous ne pouvez pas définir strictement des tuples spécifiques suivis de repos dans Typescript, il ne semble pas y avoir de moyen simple de l'implémenter tant que la fonctionnalité n'est pas ajoutée à Typescript lui-même.
Alternativement, vous pouvez générer manuellement un nombre confortable de listes de types valides dans une étape de construction distincte, mais à ce stade, je doute que la valeur gagnée en vaille la peine.
Dans JS, les paramètres de repos doivent toujours être le dernier paramètre de fonction. TS 4.0 est livré avec une fonctionnalité appelée types de tuple variadiques , que vous pouvez considérer ici comme des "paramètres de repos pour les types".
La chose intéressante est que les types de tuple variadiques peuvent être placés n'importe où dans les paramètres de fonction, pas seulement à la fin. Nous pouvons maintenant définir une quantité arbitraire d'éléments de bar
avec un dernier élément optionnel qux
.
Exemple: [bar, bar, <and arbitrary more>, qux]
peut être interprété comme un tuple variadique [...T, qux]
, où T
représente tous les éléments de la bar
.
Assert
distinct ( Playground )const arr1 = [{ bar: "bar1" }, { bar: "foo1" }, { qux: 42 }] as const const arr2 = [{ bar: "bar2" }, { bar: "foo2" }] as const const arr3 = [{ qux: 42 }] as const const typedArr1: AssertQuxIsLast<typeof arr1> = arr1 const typedArr2: AssertQuxIsLast<typeof arr2> = arr2 // error (OK) const typedArr3: AssertQuxIsLast<typeof arr3> = arr3
Vous pouvez remplacer la assert
fonction d'aide avec une sorte de type « sentinelle »:
type Bar = { bar: string }; type Qux = { qux: number }; // asserts Qux for the last index of T, other elements have to be Bar type AssertQuxIsLast<T extends readonly any[]> = { [K in keyof T]: StringToNumber[Extract<K, string>] extends LastIndex<T> ? T[K] extends Qux ? Qux : never : T[K] extends Bar ? Bar : never; }; // = calculates T array length minus 1 type LastIndex<T extends readonly any[]> = ((...t: T) => void) extends (( x: any, ...u: infer U ) => void) ? U["length"] : never; // TS doesn't support string to number type conversions ð¥, // so we support (index) numbers up to 256 export type StringToNumber = { [k: string]: number; 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99, 100: 100, 101: 101, 102: 102, 103: 103, 104: 104, 105: 105, 106: 106, 107: 107, 108: 108, 109: 109, 110: 110, 111: 111, 112: 112, 113: 113, 114: 114, 115: 115, 116: 116, 117: 117, 118: 118, 119: 119, 120: 120, 121: 121, 122: 122, 123: 123, 124: 124, 125: 125, 126: 126, 127: 127, 128: 128, 129: 129, 130: 130, 131: 131, 132: 132, 133: 133, 134: 134, 135: 135, 136: 136, 137: 137, 138: 138, 139: 139, 140: 140, 141: 141, 142: 142, 143: 143, 144: 144, 145: 145, 146: 146, 147: 147, 148: 148, 149: 149, 150: 150, 151: 151, 152: 152, 153: 153, 154: 154, 155: 155, 156: 156, 157: 157, 158: 158, 159: 159, 160: 160, 161: 161, 162: 162, 163: 163, 164: 164, 165: 165, 166: 166, 167: 167, 168: 168, 169: 169, 170: 170, 171: 171, 172: 172, 173: 173, 174: 174, 175: 175, 176: 176, 177: 177, 178: 178, 179: 179, 180: 180, 181: 181, 182: 182, 183: 183, 184: 184, 185: 185, 186: 186, 187: 187, 188: 188, 189: 189, 190: 190, 191: 191, 192: 192, 193: 193, 194: 194, 195: 195, 196: 196, 197: 197, 198: 198, 199: 199, 200: 200, 201: 201, 202: 202, 203: 203, 204: 204, 205: 205, 206: 206, 207: 207, 208: 208, 209: 209, 210: 210, 211: 211, 212: 212, 213: 213, 214: 214, 215: 215, 216: 216, 217: 217, 218: 218, 219: 219, 220: 220, 221: 221, 222: 222, 223: 223, 224: 224, 225: 225, 226: 226, 227: 227, 228: 228, 229: 229, 230: 230, 231: 231, 232: 232, 233: 233, 234: 234, 235: 235, 236: 236, 237: 237, 238: 238, 239: 239, 240: 240, 241: 241, 242: 242, 243: 243, 244: 244, 245: 245, 246: 246, 247: 247, 248: 248, 249: 249, 250: 250, 251: 251, 252: 252, 253: 253, 254: 254, 255: 255 };
type Bar = "bar" type Qux = "qux" function assert<T extends Bar[] = []>( arg: (Qux | Bar)[] extends [...T, Qux] ? never : [...T, Qux]): [...T, Qux] function assert<T extends Bar[] = []>(arg: [...T]): [...T] function assert<T extends Bar[] = []>(arg: [...T, Qux?]) { return arg }
Si vous avez vraiment besoin de paramètres de repos suivis d'un dernier élément (la spécification ne les autorise qu'à être les derniers, voir la réponse de @ Nit), voici une alternative:
as const
)StringToNumber
)Bar
/ Qux
manière récursive : bien que techniquement possible, les types récursifs ne sont pas officiellement supportés jusqu'à la version 4.1 ( Note: mise à jour ).type Sentinel<T, U extends T> = U const arr1 = ["bar", "bar"] as const const arr2 = ["qux", "bar"] as const type Arr1Type = Sentinel<Assert<typeof arr1>, typeof arr1> // OK type Arr2Type = Sentinel<Assert<typeof arr2>, typeof arr2> // error
type Assert<T extends readonly (Bar | Qux)[]> = T extends readonly [] ? T : T extends readonly [...infer I, infer U] ? I extends [] ? T : I extends Bar[] ? T : readonly [...{ [K in keyof I]: Bar }, U] : never function assert<T extends readonly (Bar | Qux)[] | readonly [Bar | Qux]>( a: Assert<T>): T { return a as T } // OK const ok1 = assert([]) const ok2 = assert(["bar"]) const ok3 = assert(["qux"]) const ok4 = assert(["bar", "qux"]) const ok5 = assert(["bar", "bar", "bar"]) const ok6 = assert(["bar", "bar", "bar", "qux"]) // errors const err1 = assert(["qux", "qux"]) const err2 = assert(["qux", "bar"]) const err3 = assert(["bar", "bar", "qux", "bar"]) const err4 = assert(["bar", "bar", "qux", "qux"])
AssertQuxIsLast
est un type de tuple mappé . Chaque clé K
dans T
est un index de la forme "0"
, "1"
, "2"
, etc. Mais ce type est une string
, pas un number
!
Afin d'affirmer Qux
pour le dernier indice, nous devons convertir K
revenir au number
afin de le comparer avec T['length']
, qui retourne la longueur du tableau number
. Comme le compilateur ne prend pas encore en charge une conversion de chaîne dynamique en nombre , nous avons créé notre propre convertisseur avec StringToNumber
.
C'est une réponse étonnamment élaborée. Merci. Ma conclusion est qu'à toutes fins pratiques, cela n'en vaudrait pas la peine.
@ last-child merci, vous êtes les bienvenus. Mis à jour avec une alternative TS 4.0
Cette fonctionnalité devrait être livrée avec TS 4.2 .
Exemple du PR de mise en œuvre :
type Foo = [...Bar[], Qux]
Votre type deviendra
function f1(...args: [...string[], number]) { const strs = args.slice(0, -1) as string[]; const num = args[args.length - 1] as number; // ... } f1(5); f1('abc', 5); f1('abc', 'def', 5); f1('abc', 'def', 5, 6); // Error
Puisque les éléments de repos doivent être les derniers, je ne vois pas de moyen simple de l'implémenter. Voir ce ticket et ce ticket pour plus de discussions sur le sujet. Cette réponse pourrait aider avec une solution alambiquée.
@Nit Merci, c'est exactement l'information que je recherchais. Si vous le postez comme réponse, je serais heureux de l'accepter.