ProgrammationDéveloppeur Frontend

Comment implémenter et typiser une fonction retournant un type union ou un tuple en fonction des paramètres ? Comment décrire correctement les valeurs de retour afin de ne pas perdre la typisation stricte lors des opérations ultérieures et de l'utilisation de la destructuration ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question :

Dans certaines tâches, il est nécessaire que la fonction puisse retourner différentes structures de données — par exemple, un tableau de longueur fixe (tuple) ou plusieurs options de résultats (type union). Dans le JavaScript dynamique, cette approche est largement utilisée pour les modèles "résultat ou erreur" (par exemple, [err, value]), tandis que TypeScript nécessite une typisation claire pour que le code ultérieur comprenne correctement la structure du résultat.

Problème :

Sans une spécification explicite du type de retour, TypeScript conduit le résultat à une forme trop "large" (c'est-à-dire non étroite) — tableau ou union, ce qui prive le développeur des avantages de l'autocomplétion et de la sécurité des types. Si l'on destructure le résultat de la fonction, il y a un risque de perdre la description précise des types des éléments.

Solution :

Il est nécessaire d'utiliser des types conditionnels et des descriptions de surcharge de fonctions, et pour les tuple — de spécifier clairement le type du tableau de résultat et d'utiliser as const (si nous retournons un tuple constant). Pour les résultats union, nous pouvons combiner les génériques et le narrowing de type, afin que le code extérieur puisse correctement distinguer le type du résultat de retour.

Exemple de code :

type Result<T> = [null, T] | [Error, null]; function parseNumber(str: string): Result<number> { const n = Number(str); return isNaN(n) ? [new Error('Nombre invalide'), null] : [null, n]; } const [err, value] = parseNumber('123'); if (err) console.error(err.message); else console.log(value!.toFixed(2)); // Avec tuple et as const pour structures auto-variées : function getStatus(flag: boolean): [string, number] | string { return flag ? ['ok', 200] as const : 'erreur'; } const r = getStatus(true); if (Array.isArray(r)) { // r: readonly [string, number] }

Caractéristiques clés :

  • Utilisez des surcharges (overloads) pour les fonctions retournant différents types en fonction des paramètres.
  • Pour les tuples, utilisez toujours [T, U] ou as const, afin de conserver la longueur et le type exacts des éléments après la destructuration.
  • Les types union des valeurs de retour nécessitent des guards de type pour une définition étroite du type lors de travaux ultérieurs.

Questions pièges.

Le type d'un élément de tableau après la destructuration de l'union d'un tuple sera-t-il toujours concret ?

Non, si la fonction retourne des unions de tuples, après la destructuration, les éléments représenteront une union de tous les types de variations possibles.

type R = [number, null] | [null, string]; const [a, b]: R = [1, null]; // a: number | null // b: null | string

Peut-on "fixer" complètement le type d'un tuple avec as const sans description de type supplémentaire pour la fonction ?

Non, as const fixe les valeurs, mais si la fonction est déclarée sans type de retour, TypeScript peut inférer un type plus large que nécessaire. Il est préférable de spécifier explicitement le type de retour.

function foo() { return [1, 'ok'] as const; } // foo(): readonly [1, "ok"]

Les surcharges avec différents types de retour garantissent-elles une définition précise du type du résultat après la destructuration ?

Les surcharges aident le compilateur, mais si le paramètre d'entrée est inconnu, le résultat sera une union de toutes les options. Pour réduire de manière précise le type, il est nécessaire d'utiliser des guards de type dans le gestionnaire de résultats.

function bar(x: number): string; function bar(x: boolean): number; function bar(x: any): any { return typeof x === 'number' ? 'str' : 123; } const r = bar(Math.random() > 0.5 ? true : 1); // r: number | string

Erreurs typiques et anti-modèles

  • Définir la fonction comme retournant any[] ou (T | U)[], ce qui entraîne un "flou" de la spécificité du tuple.
  • Essayer d'utiliser la destructuration sans narrowing de type et vérification de la validité des types des éléments.
  • Absence de description directe de la structure de la valeur de retour — cela complique le support et l'autocomplétion.

Exemple de la vie réelle

Cas négatif

La fonction retourne soit un tableau, soit une erreur, mais ne décrit pas cela explicitement dans la signature de type. Dans le code appelant, on s'attend à ce que le résultat soit un nombre, et l'accès à result[1].toFixed(2] déclenche une erreur à l'exécution.

Avantages :

  • Code facile à écrire sans typage.

Inconvénients :

  • Erreur uniquement à l'exécution.
  • Perte de typisation stricte, plus de bugs.

Cas positif

La fonction retourne un tuple strictement typé avec le type Result<T>, le traitement du résultat est construit sur la destructuration et la vérification explicite du premier élément (erreur/null) à l'exécution. Le compilateur garantit que l'on ne peut accéder au résultat qu'après un narrowing de type réussi.

Avantages :

  • Prévisibilité et clarté du code.
  • Autocomplétion automatique pour le tuple.

Inconvénients :

  • Nécessité de décrire les types manuellement.
  • Augmentation du nombre de guards de type et des vérifications.