Comment taper une fonction qui vérifie si un argument étend le type d’un argument et renvoie l’argument en TypeScript ?
Je veux taper une fonction qui vérifie que le type de son argument étend une variable de type, puis renvoie l'argument.
Comme ceci :
declare function extendsA<I, X extends I>(argument:X): X
Mais quand je l'utilise, TypeScript se plaint que la fonction prend deux arguments de type. Ou ceci :
declare function extendsB<I, X extends I = I>(argument:X): X
Et cela renvoie le mauvais type.
Lien du terrain de jeu
Comment puis-je faire en sorte que TypeScript prenne le type de argument, le mette dans la variable de type X, s'assure qu'il étend I et retourne ensuite X ?
Un cas d'utilisation serait :
const recordOfNumberArray = extendsA<{[K: string]: readonly number[]}>({
"hello":[10,200]
} as const); // good
recordOfNumberArray. // with intellisense of all keys
const recordOfNumberArray = extendsA<{[K: string]: readonly number[]}>({
"hello":[10,200,"hi"]
} as const); // should error
Réponse #1
Ah, vous essayez de mettre en œuvre le soi-disant opérateur « satisfies », comme demandé dans microsoft/TypeScript#7481. L'idée est que vous pourriez écrire val satisfies Type, et il y aurait une erreur de compilation si val n'est pas de type Type, mais cela ne généralise pas val au type Type. Malheureusement, un tel opérateur n'existe pas encore dans TypeScript, donc vous recourez à une solution de contournement. Je vais renommer extendsA en satisfies à partir de maintenant.
Ainsi, vous ne pouvez pas facilement implémenter satisfies en tant que fonction générique unique avec deux paramètres de type, puisque TypeScript ne dispose pas non plus de l'inférence partielle des paramètres de type comme demandé dans microsoft/TypeScript#26242. Soit le compilateur infère tous les paramètres de type à partir de l'appel, soit vous devez spécifier manuellement tous les paramètres de type. Comme vous l'avez découvert, une valeur par défaut pour un paramètre de type générique ne peut pas être utilisée pour obtenir une inférence partielle, puisque tout ce que vous obtiendrez est la valeur par défaut si vous omettez le paramètre.
Au lieu de cela, la façon normale de contourner ce problème est de refactoriser en une fonction currifiée, où la fonction prend uniquement le paramètre de type générique à spécifier manuellement, puis elle renvoie une autre fonction qui prend le paramètre de type à inférer. Comme ceci :
// declare function satisfies<I>(): <X extends I>(argument: X) => X;
function satisfies<I>() {
return <X extends I>(argument: X) => argument
}
Et donc vous devez l'appeler ainsi, avec une étape d'appel de fonction supplémentaire :
satisfies<{ [K: string]: readonly number[] }>()({
"hello": [10, 200]
} as const); // good
Ce n'est pas merveilleux, bien que la douleur puisse être atténuée si vous vous surprenez à vérifier le même type I plus d'une fois, car vous pouvez réutiliser la fonction partiellement appliquée :
const satisfiesRecordOfNumberArray =
satisfies<{ [K: string]: readonly number[] }>();
const recordOfNumberArray = satisfiesRecordOfNumberArray({
"hello": [10, 200]
} as const); // good
recordOfNumberArray.hello // okay
const recordOfNumberArray2 = satisfiesRecordOfNumberArray({
"hello": [10, 200, "hi"]
} as const); // error!
// Type 'readonly [10, 200, "hi"]' is not assignable to type 'readonly number[]'
La seule autre approche à laquelle je peux penser est de garder les deux paramètres de type dans une fonction, I et X, mais d'ajouter un paramètre de fonction factice dummyI de type I. C'est probablement encore pire que le currying, car cela vous oblige à fournir une valeur factice de type I, ou à faire semblant de le faire :
// declare function satisfies<I, X extends I>(dummyI: I, argument: X): X;
function satisfies<I, X extends I>(dummyI: I, argument: X) { return argument }
const recordOfNumberArray = satisfies(null! as { [K: string]: readonly number[] }, {
"hello": [10, 200]
} as const); // good
const recordOfNumberArray2 = satisfies(null! as { [K: string]: readonly number[] }, {
"hello": [10, 200, "hi"]
} as const); // error!
// Type 'readonly [10, 200, "hi"]' is not assignable to type 'readonly number[]'
Ça fonctionne, mais maintenant au lieu de satisfies<I>()(val), vous avez satisfies(null! as I, val). Comme je l'ai dit, probablement pire, bien que ce soit subjectif. Idéalement, vous n'auriez besoin d'aucun appel de fonction et il y aurait un opérateur satisfies intégré. Sans opérateur intégré, le passage d'un appel de fonction à deux appels de fonction ne semble pas beaucoup pire que le passage de zéro à un.
Lien du terrain de jeu vers le code
