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

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 principalif (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 鈥淚nnecesaria鈥 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 鈥渁ny鈥 o 鈥渦nknown鈥 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 鈥淯nion 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;
}