No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

No se trata de lo que quieres comprar, sino de quién quieres ser. Aprovecha el precio especial.

Antes: $249

Currency
$209

Paga en 4 cuotas sin intereses

Paga en 4 cuotas sin intereses
Suscríbete

Termina en:

13 Días
23 Hrs
54 Min
4 Seg

Sobrecarga de funciones: la solución

11/22
Recursos

Cuando el tipado del retorno de una función puede ser más de un tipo de dato (por ejemplo, que el retorno pueda ser string, number o boolean), TypeScript en primera instancia no permite utilizar los métodos propios de un tipo de dato específico a menos que se realice una validación de tipos previamente.

Retorno de funciones con más de un tipo de dato

Supongamos que tenemos una función que puede recibir como parámetro un valor de tipo string o string[] (un array con elementos de tipo string) y retorne lo inverso, osea un string[] si se envía un string o un string si manda un string[]:

// Nico => [N,i,c,o] || Entrada: string => Salida: string[]
// [N,i,c,o] => Nico || Entrada: string[] => Salida: string

function parseStr(input: string | string[]): string | string[] {
  if (Array.isArray(input)) {
    return input.join(''); // string
  } else {
    return input.split(''); // string[]
  }
}

Invoquemos a la función y guardemos su retorno en una variable:

// Nico => [N,i,c,o] || Entrada: string => Salida: string[]
// [N,i,c,o] => Nico || Entrada: string[] => Salida: string

function parseStr(input: string | string[]): string | string[] {
  if (Array.isArray(input)) {
    return input.join(''); // string
  } else {
    return input.split(''); // string[]
  }
}

// 👇
const rptaStr = parseStr(['N','I','C','O']); // Retorna un string
console.log('rptaStr', "['N','i','c','o'] =>", rptaStr);

Como podemos notar a rptaStr se le es asignado un valor de tipo string el cual es el tipado del retorno de la función en este caso. Sin embargo, si intentamos aplicar un método propio de los string como por ejemplo toLowerCase (convierte a minúscula los caracteres), TypeScript nos marcará error:

// Nico => [N,i,c,o] || Entrada: string => Salida: string[]
// [N,i,c,o] => Nico || Entrada: string[] => Salida: string

function parseStr(input: string | string[]): string | string[] {
  if (Array.isArray(input)) {
    return input.join(''); // string
  } else {
    return input.split(''); // string[]
  }
}

const rptaStr = parseStr(['N','I','C','O']); // Retorna un string
rptaStr.toLowerCase(); // ⛔ Error
console.log('rptaStr', "['N','i','c','o'] =>", rptaStr);

Validación de tipos

Ante el problema mostrado anteriormente, podríamos validar el tipo de dato del retorno de la función antes de utilizar el método correspondiente a dicho tipo:

// Nico => [N,i,c,o] || Entrada: string => Salida: string[]
// [N,i,c,o] => Nico || Entrada: string[] => Salida: string

function parseStr(input: string | string[]): string | string[] {
  if (Array.isArray(input)) {
    return input.join(''); // string
  } else {
    return input.split(''); // string[]
  }
}

const rptaStr = parseStr(['N','I','C','O']); // Retorna un string

// Validación de tipos
if (typeof rtaStr === 'string') { // 👈
  rtaStr.toLowerCase(); // ✅ Ya podemos utilizar los métodos sin problemas
}

console.log('rptaStr', "['N','i','c','o'] =>", rptaStr);

Sobrecarga de funciones en TypeScript

La sobrecarga de funciones nos permite definir varias declaraciones de una función con el mismo nombre que puedan recibir diferentes parámetros y/o con diferente tipado. A estas declaraciones se les suelen llamar firmas y la última firma en declarar es la que tendrá la implementación de la función, mientras las otras se quedarán solo declaradas sin código dentro.

Sobrecarga de funciones en vez de la validación de tipos

Podemos usar esta característica presente en TypeScript para ahorrarnos la validación de tipos, como por ejemplo en el problema que hemos visto más arriba con la función parseStr:

// Nico => [N,i,c,o] || Entrada: string => Salida: string[]
// [N,i,c,o] => Nico || Entrada: string[] => Salida: string

// Sobrecarga de funciones 👇
function parseStr(input: string): string[]; // 👀
function parseStr(input: string[]): string; // 👀

function parseStr(input: unknown): unknown { // Función principal
  if (Array.isArray(input)) {
    return input.join(''); // string
  } else {
    return input.split(''); // string[]
  }
}

const rptaStr = parseStr(['N','I','C','O']); // Retorna un string
// Usaremos un método propio del tipo de dato "string"
rtaStr.toLowerCase(); // ✅ No necesitamos de la validación de datos para usar los métodos de este tipo de dato
console.log('rptaStr', "['N','i','c','o'] =>",rptaStr);

const rptaArray = parseStr('Nico'); // Retorna un string[] (un array de elementos de tipo string)
// Usaremos un método propio del tipo de dato "string[]"
rtaArray.reverse(); // ✅ No necesitamos de la validación de datos para usar los métodos de este tipo de dato
console.log('rptaArray', 'Nico =>', rptaArray);

Puesto que en las firmas adicionales (sobrecargas) de la función parseStr ya manejamos los tipos de datos string y string[], el tipado tanto de los parámetros y como del retorno de la firma que contiene la lógica de la función puede ser del tipo unknown o any.

Contribución creada por: Martín Álvarez (Platzi Contributor).

Aportes 19

Preguntas 4

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Resumen de la solucion

En conclusión, lo que vamos a hacer es escribir de nuevo la función con los parámetros y su tipo de dato de retorno antes de declarar la función como tal, para que de esa forma TS sepa en que casos se retorna cierto valor. Se que no le entendiste, veamos un ejemplo

type customType = string | string[];

function parseStr(arg: string): string[]
function parseStr(arg: string[]): string

function parseStr(arg: customType): customType {
  // code here...
}

Las dos primeras funciones parseStr son las que se le llama sobrecarga de funciones y le ayuda a TS a predecir que tipo de dato retornar en ciertos casos específicos.

Les comparto mis apuntes. 😄

Uso de sobrecarga de funciones

Para realizar la sobrecarga de funciones tenemos simplemente declarar otras funciones con el mismo nombre de la función que tiene la lógica implementada. Dentro de los parámetros de las nuevas funciones vamos a definir el tipo de dato que se va a recibir y además tenemos que aclarar el tipo de dato que se va a retornar con ese parámetro.

Además, en la función que tiene toda la lógica a sus parámetros podemos colocarle como tipo de dato unknown al igual que su retorno.

Sintaxis

function functionName (parameter: dataType): dataTypeReturn;
function functionName (parameter: dataType): dataType {
	statements
}

Buenas prácticas

  • Cuando tengamos sobre carga de métodos y por alguna razón tengamos un unknown o any, en esa sobre carga, lo mejor es dejar ese unknowno any al final. Caso contario no funcionará correctamente esa aserción de tipos y por ende el autocompletado del editor.
  • Evaluar si realmente se necesita una sobre carga o simplemente puedes buscar otra forma de hacerlo como ser usando valores opcionales.

Código de la clase

// Galeed -> ['G', 'a', 'l', 'e', 'e', 'd']
// ['G', 'a', 'l', 'e', 'e', 'd'] -> Galeed
// number -> boolean

function parseStr (input: string): string[];
function parseStr (input: string[]): string;
function parseStr (input: number): boolean;
function parseStr (input: unknown): unknown {
    if (Array.isArray(input)) return input.join(''); // string
    if (typeof(input) === 'string') return input.split(''); // string[]
    if (typeof(input) === 'number') return true; // boolean
}

const rtaArray = parseStr('Galeed');
rtaArray.reverse();
const rtaString = parseStr(['G', 'a', 'l', 'e', 'e', 'd']);
const rtaNumber = parseStr(1);
console.log('rtaArray: Galeed -> ', rtaArray);
console.log("rtaString: ['G', 'a', 'l', 'e', 'e', 'd'] -> ", rtaString);
console.log("rtaNumber: number -> ", rtaNumber);

Al estar escuhando los videos al 1.5 lo veo como normal, pero al colocarlo a 1 es como si la normal es super lenta

También es posible manejar la sobrecarga con arrow functions, de esta manera lo hice yo:

type parseStrIO = {
  (input: string): string[],
  (input: string[]): string
}

export const parseStr: parseStrIO = (input: any) => {
  if(Array.isArray(input)){
    return input.join("");
  }else{
    return input.split("");
  }
}

const inputStr = "jason";
const inputArray = ["j", "a", "s", "o", "n"];
const outputArray = parseStr(inputStr);
const outputStr = parseStr(inputArray);

console.log(inputStr, " ==> ", outputArray);
console.log(inputArray, " ==> ", outputStr);
console.log("TS detects an array: ", outputArray.includes('a'));
console.log("TS detects an string: " , outputStr.toUpperCase());

Veo que en el ejemplo de typeORM se hace una sobrecarga “Innecesaria” ya que los tres overload retornan el mismo tipo de dato y luego además al implementar la función ponen los parámetros como opcionales usando el ?. Será que si está tan mala esa práctica? o por qué lo hicieron?

Encontré una manera de hacerlo con arrow functions:

type IOverload = {
    (param: number): number[];
    (param: object): object[];
}

const overloadedArrowFunc: IOverload = (param: any) => {
    return [param, param];
}

let val = overloadedArrowFunc(4);

Como en la function refactorizada se definió un input de tipo unknow, decidí probar pasandole un booleano que fue de los tipos de datos que no definimos en la sobrecarga y como resultado es muy interesante como TS sigue verificando los input según la diferentes opciones que declaramos previamente y como no hace match con ninguna entonces arroja una advertencia

Uno de los principales beneficios de las sobrecargas es la ayuda que nos brindan (a través del Intellisense) a la hora de programar:

Aquí me recuerda mucho el Multiple Dispatch de Julia. En eso sí es más poderos o mejor implimentado

**Sobrecarga de funciones:** Declarar la misma función con diferentes parametros antes de definir su lógica interna.

pues si se trata solo por cantidad de parametros, no me suena la idea de usar sobre carga. para eso puedo usar un optional o nulish.

pero si se trada de un typo de dato especifico para retornar de acuerdo a los parametros, por supuesto!

Buenas prácticas

Sobrecarga de funciones

Hay situaciones en las que necesitamos enviarle parámetros a una función para que efectúe acciones basándonos en el tipo de esos parámetros, y JS permite hacerlo, aunque no con mucha seguridad, como en este caso:

function parseStr(input: string | string[]): string | string[] {
  if (Array.isArray(input)) return input.join('');
  else return input.split('');
}

const rtaArray = parseStr('yilmar');
if (Array.isArray(rtaArray)) rtaArray.reverse();
console.log('rtaArray', 'yilmar =>', rtaArray);

const rtaStr = parseStr(['y', 'i', 'l', 'm', 'a', 'r']);
if (typeof rtaStr === 'string') rtaStr.toLowerCase();
console.log('rtaStr', "['y', 'i', 'l', 'm', 'a', 'r'] =>", rtaStr);

La limitante que tiene este código es que al momento de llamar a la función tenemos ayuda del editor, ya que TS no puede determinar que tipo de dato está recibiendo exactamente, además, debemos hacer validaciones de tipo para poder usar la función.
Para solucionar este problema existe la sobrecarga de funciones:

export function parseStr(input: string): string[];
export function parseStr(input: string[]): string;

export function parseStr(input: string | string[]): string | string[] {
  if (Array.isArray(input)) return input.join('');
  else return input.split('');
}

const rtaArray = parseStr('yilmar');
console.log('rtaArray', 'yilmar =>', rtaArray);

const rtaStr = parseStr(['y', 'i', 'l', 'm', 'a', 'r']);
console.log('rtaStr', "['y', 'i', 'l', 'm', 'a', 'r'] =>", rtaStr);

En esta versión del código el editor nos ayuda más y ya no tenemos que hacer validaciones de tipo.
.

Buenas prácticas

  • Si una función tiene sobrecargas y una de ellas recibe parametros de tipo “any” o “unknown” esta sobrecarga debe ir al final.
  • Si una función puede usar una cantidad variable de parámetros, pero en todos los casos retorna algo del mismo tipo, sería mejor usar parámetros opcionales.
  • Si una función puede usar parámetros de diferente tipo pero la salida es del mismo tipo, se puede usar “Union Type” y evitar una sobrecarga.

Encontré esta manera de hacer ese ejercicio ya que cuando trato de usar unknown no me funciona sale error creo que es por la versión pero no estoy seguro

interface StringArrayResult {
  kind: 'stringArray';
  value: string[];
}

interface StringResult {
  kind: 'string';
  value: string;
}

interface BooleanResult {
  kind: 'boolean';
  value: boolean;
}

type ParseStrResult = StringArrayResult | StringResult | BooleanResult;

export function parseStr(input: string): StringArrayResult;
export function parseStr(input: string[]): StringResult;
export function parseStr(input: number[]): BooleanResult;

export function parseStr(input: unknown): ParseStrResult {
  if (Array.isArray(input)) {
    return { kind: 'stringArray', value: input.join('') };
  } else if (typeof input === 'string') {
    return { kind: 'string', value: input.split(',') };
  } else if (typeof input === 'number') {
    return { kind: 'boolean', value: input > 0 };
  }

  throw new Error('Unsupported input type');
}

// Ejemplo de uso
const rtaArray = parseStr(['j', 'u', 'a', 'n']);
console.log('Aqui convertimos el array a string ', rtaArray.value);

const rtaArrayV2 = parseStr('j,u,a,n');
console.log('Aqui convertimos el string a array ', rtaArrayV2.value);
 

Entendí que una función puede ser sobre cargada para definirle que reciba o devuelva tipos de datos distintos según se requiera.

function parseStr(input: string): string[];
function parseStr(input: string[]): string;
function parseStr(input: number): boolean;

function parseStr(
 input: unknown
): unknown {

	if(Array.isArray(input)) { // asercion
		return input.join("") // string
	}else if(typeof input === 'string') {
		return input.split("") // string[]
	} else if(typeof input === 'number') {
		return true // boolean
	}
}

const rta1 = parseStr("Virus")
rta1.reverse()

const rta0 = parseStr(["V","i","r","u","s"])
rta0.toLowerCase()

lo vi en mucho código de terceros, pero nunca supe que se trataba de sobrecarga de funciones
la verdad, muy útil

Además de las funciones regulares, los métodos en las clases también pueden sobrecargarse.

Codigo de las mejores practicas

First Case: uknnown is not at the end

declare function fn(x: unknown): unknown;
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
var myElement: HTMLDivElement;
var x = fn( myElement ); // x: string 


//Sol: keep the unknown at the end
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: unknown): unknown;
var myElement: HTMLDivElement;
var x = fn( myElement ); // x: string 

Second case: create unnesesary overloads

interface Example {
  diff(one: string): number;
  diff(one: string, two: string): number;
  diff(one: string,  two: string, three:boolean): number;
}

// Sol: Create one with option parameters
interface Example {
  diff(one: string, two?: string, three?: string): number;
}

Third case: create multiple lines, but at the end just return the same type (Moment in this case)

interface Moment {
  utcOffset(): number;
  utcOffset(b: number): Moment;
  utcOffset(b: string): Moment;
}

// solution: just use 1 union type 
interface Moment {
  utcOffset(): number;
  utcOffset(b: number | string): Moment;
}