Historia de la pregunta:
En algunas tareas se requiere que la función pueda devolver diferentes estructuras de datos: por ejemplo, un arreglo de longitud fija (tupla) o varios resultados alternativos (tipo unión). Este enfoque se utiliza ampliamente en JavaScript dinámico para patrones de "resultado o error" (por ejemplo, [err, value]), y en TypeScript se requiere una tipificación clara para que el código posterior comprenda correctamente la estructura del resultado.
Problema:
Sin especificar explícitamente el tipo de retorno, TypeScript derivará el resultado a una forma demasiado "amplia" (es decir, no específica): un arreglo o una unión, lo que priva al desarrollador de las ventajas de autocompletado y seguridad de tipos. Si se desestructura el resultado de la función, hay riesgo de perder la descripción exacta de los tipos de los elementos.
Solución:
Es necesario utilizar tipos condicionales y descripciones de sobrecarga de funciones, y para la tupla, especificar explícitamente el tipo del arreglo resultante y utilizar as const (si se devuelve una tupla constante). Para los resultados de tipo unión, se pueden combinar genéricos y narrowing de tipos para que el código exterior pueda diferenciar correctamente el tipo del resultado devuelto.
Ejemplo de código:
type Result<T> = [null, T] | [Error, null]; function parseNumber(str: string): Result<number> { const n = Number(str); return isNaN(n) ? [new Error('Número inválido'), null] : [null, n]; } const [err, value] = parseNumber('123'); if (err) console.error(err.message); else console.log(value!.toFixed(2)); // Con tupla y as const para estructuras auto-determinadas: function getStatus(flag: boolean): [string, number] | string { return flag ? ['ok', 200] as const : 'error'; } const r = getStatus(true); if (Array.isArray(r)) { // r: readonly [string, number] }
Características clave:
¿El tipo del elemento del arreglo después de la desestructuración de la unión de la tupla será siempre específico?
No, si la función devuelve una unión de tuplas, después de la desestructuración, los elementos representarán la unión de todos los tipos posibles.
type R = [number, null] | [null, string]; const [a, b]: R = [1, null]; // a: number | null // b: null | string
¿Se puede "fijar" completamente el tipo de la tupla con as const sin una descripción adicional del tipo de la función?
No, as const fija los valores, pero si la función se declara sin un tipo de retorno, TypeScript puede derivar el tipo más ancho de lo necesario. Es mejor especificar explícitamente el type return.
function foo() { return [1, 'ok'] as const; } // foo(): readonly [1, "ok"]
¿Las sobrecargas con diferentes tipos de retorno garantizan una definición del tipo de resultado después de la desestructuración?
Las sobrecargas ayudan al compilador, pero si el parámetro de entrada es desconocido, el resultado será una unión de todas las opciones. Para un narrowing exacto del tipo, se deben utilizar type guards en el manejador del resultado.
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
La función devuelve un arreglo o un error, pero no lo describe explícitamente en la firma del tipo. En el código que llama, se espera que el resultado sea un número, y el acceso a result[1].toFixed(2) lanza un error en tiempo de ejecución.
Pros:
Contras:
La función devuelve una tupla tipada estrictamente con el tipo Result<T>, el procesamiento del resultado se basa en la desestructuración y la verificación explícita en tiempo de ejecución del primer elemento (error/null). El compilador garantiza que se puede acceder al resultado solo después de un narrowing exitoso del tipo.
Pros:
Contras: