No tienes acceso a esta clase

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

Sobrecarga de funciones: la solución

11/22
Recursos

Aportes 15

Preguntas 3

Ordenar por:

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

o inicia sesión.

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);

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?

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());

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

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

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;
}