Funciones en TypeScript

Las funciones son una de las bases de cualquier aplicación escrita en JavaScript. Se utilizan para construir capas de abstracción, para construir componentes que actúan como clases, para ocultar información (ocultación de información) y para actuar también como módulos. Aunque las clases, los espacios de nombres y las unidades existen en TypeScript, las funciones siguen desempeñando el papel principal en la descripción de cómo se hacen las cosas. TypeScript también agrega algunas características nuevas a las funciones estándar de JavaScript para que sea más fácil trabajar con ellas.

Funciones

Las funciones se pueden crear inicialmente en TypeScript como en JavaScript, las funciones se pueden crear como una función con nombre o una función anónima. Esto le permite elegir la forma más conveniente para usted en su aplicación, ya sea que esté creando varias funciones en una API o una función de un solo uso para pasar a otra función (el nombre de la función no importa en este caso).

Como un simple recordatorio, aquí están ambos métodos en JavaScript:

// función nombrada 
function add(x, y) {
    return x + y;
}

// función anónima 
let myAdd = function(x, y) { return x + y; };

Como en JavaScript, las funciones pueden referirse a variables fuera del cuerpo de la función. Cuando hacemos esto, estamos diciendo que estamos «capturando» estas variables. Si bien está fuera de los objetivos de esta guía comprender cómo funciona la captura y sus inconvenientes, comprender cómo funciona es importante para los programadores de JavaScript y TypeScript.

let z = 100;

function addToZ(x, y) {
    return x + y + z;
}

Tipos de funciones

Adición a tipos de funciones

Agreguemos los tipos a los dos ejemplos anteriores:

function add(x: number, y: number): number {
    return x + y;
}

let myAdd = function(x: number, y: number): number { return x + y; };

Podemos agregar los tipos para cada parámetro y luego a la función misma para agregar el tipo de retorno. TypeScript puede inferir el tipo de devolución strings mirando  return, por lo que a menudo se puede prescindir de él.

Escribir el tipo de función

Después de agregar los tipos de parámetros y el tipo de retorno a la función, escribamos el tipo de función completo observando cada parte del tipo de función:

let myAdd: (x: number, y: number) => number =
    function(x: number, y: number): number { return x + y; };

Los tipos de función tienen las mismas dos partes: el tipo de operandos y el tipo de valor devuelto. Se requieren ambas partes al escribir el tipo completo de función. Escribimos los tipos de parámetros como lo hacemos para las listas de parámetros, dando a cada parámetro un nombre y un tipo. Este nombre solo ayuda con la legibilidad del código y los nombres no tienen que ser idénticos, ya que podríamos haber escrito el código anterior de la siguiente manera:

let myAdd: (baseValue: number, increment: number) => number =
    function(x: number, y: number): number { return x + y; };

Siempre que los tipos de parámetros estén organizados correctamente, es un tipo válido de la función, independientemente de los nombres de los parámetros en el tipo de función.

La segunda parte es del tipo recursivo. La parte del tipo de devolución se ilustra con la flecha =‎> para ‎‎separar los parámetros del tipo de devolución. Esta parte, como se mencionó anteriormente, se requiere en el tipo de la función, por lo que si la función no tiene valor de retorno, use el tipo void en lugar de dejarlo.

Tenga en cuenta también que solo los operandos y el tipo de retorno constituyen el tipo de una función. Las variables capturadas no están en tipo. Entonces, las variables capturadas son parte del «estado oculto» de cualquier función y no parte de su API.

Tipo de inferencia

El compilador de TypeScript puede inferir el tipo si especifica el tipo en un lado y no en el otro:

// El compilador conoce el tipo completo de la función incluso si no se especifica en el lado izquierdo 
let myAdd = function(x: number, y: number): number { return  x + y; };

// Aquí también, el tipo de función completo se define desde el lado izquierdo de la declaración 
let myAdd: (baseValue: number, increment: number) => number =
    function(x, y) { return x + y; };

Esto se llama escritura contextual, y es una forma de inferencia de tipos, esto ayuda a reducir el esfuerzo que se necesita para agregar tipos a los programas.

Parámetros predeterminados y opcionales

Se requiere pasar valores para todos los parámetros de una función para que su llamada funcione correctamente en TypeScript. Pero esto no significa que no se pueda pasar el valor nullundefined, significa que el compilador verificará que el usuario haya pasado un valor para cada parámetro de la función. El compilador también considera que estos parámetros son los únicos parámetros que se pasarán a la función. En otras palabras, la cantidad de valores pasados ​​a la función debe coincidir con la cantidad de operandos que espera la función.

function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");   // Error, no hay suficientes parámetros 
let result2 = buildName("Bob", "Adams", "Sr.");   // Error más transacciones de las esperadas 
let result3 = buildName("Bob", "Adams");   // el numero de transacciones es bueno

En JavaScript, todos los parámetros son opcionales y los usuarios pueden dejar algunos según corresponda. Su valor undefined es cuando se deja y no se pasa. Podemos obtener esta función agregando el carácter char » ? «a los últimos parámetros que queremos que sean opcionales. Por ejemplo, supongamos que queremos que el parámetro lastName del ejemplo anterior sea opcional:

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

let result1 = buildName("Bob");   // Funciona correctamente 
let result2 = buildName("Bob", "Adams", "Sr.");  // Error, más transacciones de las esperadas 
let result3 = buildName("Bob", "Adams");  // el numero de transacciones es bueno

Los operandos opcionales siempre deben agregarse y estar después de los operandos solicitados. Si queremos que el operando sea firstName opcional en lugar del operando lastName, debemos cambiar el orden de los operandos en la función colocando el operando firstName al final de la lista de operandos.

En TypeScript también podemos establecer un valor que el parámetro mantendrá si el usuario no pasa un valor diferente, o si el usuario le pasa  undefined. Se denominan parámetros inicializados por defecto. Tomemos el ejemplo anterior y demos al parámetro lastName el valor ‎"Smith"‎.

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");   // "Bob Smith"
let result2 = buildName("Bob", undefined);  // "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr.");  // Error más transacciones de las esperadas 
let result4 = buildName("Bob", "Adams");   // el numero de transacciones es bueno

De forma predeterminada, los operandos inicializados que están después de todos los operandos requeridos son opcionales y se pueden dejar para pasar un valor cuando se llama a la función como operandos opcionales. Esto significa que los parámetros opcionales y los parámetros predeterminados al final de la lista comparten sus tipos. Entonces cada una de las funciones

function buildName(firstName: string, lastName?: string) {
    // ...
}

y la funcion

function buildName(firstName: string, lastName = "Smith") {
    // ...
}

Comparten el mismo tipo ‎(firstName:‎ string‎,‎ lastName‎?‎:‎ string‎)‎ ‎=>‎ string‎. De modo que desaparece el valor por defecto del parámetro en lastName, manteniendo el hecho de que el parámetro es un parámetro opcional.

A diferencia de los parámetros opcionales normales, los parámetros configurados por defecto no necesitan ser posteriores a los parámetros solicitados. Si un parámetro se inicializa de manera predeterminada antes que un parámetro obligatorio, los usuarios deberán pasar el valor undefined explícitamente para obtener el valor predeterminado. Por ejemplo, el ejemplo anterior podría escribirse solo firstName:

function buildName(firstName = "Will", lastName: string) {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");      // Error, no hay suficientes parámetros 
let result2 = buildName("Bob", "Adams", "Sr.");  // Error más transacciones de las esperadas 
let result3 = buildName("Bob", "Adams");          // "Bob Adams" 
let result4 = buildName(undefined, "Adams");      // "Will Adams"

Parámetros restantes

Los parámetros obligatorios, opcionales y predeterminados tienen una característica en común: solo tienen un parámetro a la vez. A veces, es posible que desee trabajar con varios parámetros como un grupo, o es posible que no sepa cuántos parámetros tomará una función. Puede trabajar con parámetros pasados ​​directamente a través de la variable arguments que está visible dentro del cuerpo de cada función.

En TypeScript puede combinar estos parámetros en una sola variable:

function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

Los parámetros restantes se tratan como un número ilimitado de parámetros opcionales. Al pasar valores a un parámetro residual, puede pasar cualquier número de valores y tampoco se puede pasar ningún valor. El compilador construirá una matriz que contiene los valores pasados, esta matriz se llamará con el nombre dado después de los tres puntos ( ...) , lo que le permitirá acceder a estos valores dentro de la función.

Los tres puntos también se utilizan con los parámetros restantes del tipo de función:

function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}

let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

Palabra clave this

Aprender a usar el interruptor this en JavaScript es una fase por la que todo desarrollador debe pasar. Los desarrolladores de TypeScript necesitan aprender cómo usar this. TypeScript le alerta sobre usos inapropiados del mismo dependiendo de algunas tecnologías. 

Funciones de palabra clave this y funciones flecha(arrow functions)

La palabra clave this en JavaScript es una variable que se establece cuando se llama a la función. Esto lo convierte en una función poderosa y flexible, pero siempre necesita conocer el contexto en el que se ejecuta la función. Esto suele ser confuso, especialmente cuando se devuelve una función o se pasa una función como argumento. Veamos un ejemplo sencillo:

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        return function() {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

Observe que createCardPicker una función devuelve otra función. Si intentamos ejecutar el ejemplo, obtendremos un error en lugar del cuadro de alerta esperado. Esto se debe a que el this utilizado dentro de la función generada se configurará createCardPicker para que sea el valor window en lugar del objeto deck. Esto se debe a que llamamos a la función ‎cardPicker‎()‎ forma independiente. Una llamada de sintaxis sin método de nivel superior como esta usaría el window this. (Tenga en cuenta que el valor de ‘ this será undefined‘ en lugar de ‘ window en modo estricto strict).

Podemos resolver este problema asegurándonos de que la función this esté correctamente antes de devolver la función para su uso posterior. De esta manera, la variable en la función aún podrá acceder al objeto deck original sin importar cómo se use la función más adelante. Para hacer esto, cambiaremos la expresión de la función para usar la función arrow que viene con ECMAScript 6. Las funciones de flecha capturan la variable this donde se crea la función en lugar de donde se llama:

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // Observe que la línea de abajo se ha convertido en una función de flecha, permitiéndonos Al recoger la variable aquí 
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

Si pasa la bandera al ‎-‎-‎noImplicitThis. Te avisará que el  this que está en ‎this.suits[pickedSuit]‎ es de tipo any.

Actas this

Sigue siendo ‎this‎.‎suits‎[pickedSuit]‎ any amable por desgracia. Esto se debe a que la variable this proviene de la expresión de la función dentro del objeto literal. Se puede pasar un parámetro this explícito arreglar esto. Los operandos this son parámetros espurios que aparecen primero en la lista de parámetros de la función:

function f(this: void) {
    // asegúrese de que `this` 
    // no se pueda usar dentro de esta función 
}

Para agregar algunas interfaces a nuestro ejemplo anterior, agregaremos los tipos CardDeck para que los tipos sean más claros y reutilizables:

interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // Tenga en cuenta que la función ahora establece explícitamente que su destinatario debe ser de tipo Deck 
    createCardPicker: function(this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

TypeScript ahora espera createCardPicker ser llamado en un objeto de tipo Deck. Esto significa que la variable this ahora es de tipo Deck, y su tipo ya no es de tipo any, por lo que la bandera ‎-‎-‎noImplicitThis‎ no producirá ningún error.

Parámetros this en devoluciones de llamada

También pueden ocurrir errores al trabajar con funciones de devolución de llamada this, al pasar funciones a una biblioteca a la que llama más tarde. La razón de esto es que la biblioteca que llama a su devolución de llamada la llamará como una función normal, por lo que la variable this contendrá el valor undefined. También puede usar los operadores this para evitar errores de devolución de llamada. Primero, el escritor de la biblioteca deberá anotar el tipo de devolución de llamada de la variable this de la siguiente manera:

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}

Aquí, la expresión ‎this: void‎ significa que la función ‎addClickListener‎ espera onclick que sea ​​una función que no requiere un tipo this. En segundo lugar, agregue una nota al pie de página al código que llama a la función con la variable this de la siguiente manera:

class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // Falso, estamos usando this 
        // aquí, por lo que llamar a esta devolución de llamada fallará 
        this.info = e.message;
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad);  // Error

Después de agregar una anotación de tipo a la variable this, esto dice explícitamente que la función onClickBad debe llamarse desde una instancia de la clase Handler. TypeScript detectará entonces que addClickListener requiere una función propietaria de la nota al pie this: void. Para corregir este error, cambie el tipo this de la siguiente manera:

class Handler {
    info: string;
    onClickGood(this: void, e: Event) { 
        // La variable this no se puede utilizar aquí porque es de tipo void 
        console.log('clicked!');
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);

Debido a que la función onClickGood define this su propio tipo como si fuera su propio tipo void, se puede pasar a addClickListener. Pero, por supuesto, esto significa que no podrá acceder ‎this.info‎. Si desea ambas funciones, tendrá que usar una función de flecha:

class Handler {
    info: string;
    onClickGood = (e: Event) => { this.info = e.message }
}

Esto funciona porque las funciones de flecha no capturan la variable this, por lo que siempre puede pasarla a lo que espera una variable this de tipo void‎this: void). La desventaja de este método es que la función de flecha se crea para cada objeto de tipo Handler. Los métodos se crean una sola vez y se adjuntan a la cadena de prototipos del Handler. Es compartido por todos los objetos de tipo Handler.

Sobrecargas

JavaScript es bastante dinámico, y es común que una sola función de JavaScript devuelva diferentes tipos de objetos según la forma de los parámetros que se les pasan.

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x): any { 
    // Comprobamos si estamos trabajando con un objeto o una matriz 
   // Si es así, elegimos la carta del conjunto de cartas 
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Si no, dejamos que el usuario escoja la carta 
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

La función pickCard aquí tiene dos tipos de datos pasados ​​por el usuario. Si pasa un objeto que representa una baraja de cartas, la función seleccionará la carta. Si elige una carta, le diremos cuál eligió. Pero, ¿cómo se le puede describir esto a un verificador de especies?

La respuesta es agregar varios tipos de funciones a la misma función como una lista de sobrecargas. Esta lista es lo que usará el compilador para juzgar las llamadas a funciones. Vamos a crear una lista de sobrecarga que describa lo que acepta la función pickCard y lo devolverá:

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // Comprobamos si estamos trabajando con un objeto o una matriz 
    // Si es así, elegimos la carta del conjunto de cartas 
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
  // Si no, permitimos que el usuario elija la carta 
  else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

Con este cambio, las sobrecargas ahora agregan verificación de tipos a pickCard. Para que el compilador elija verificar los tipos correctamente, realiza un proceso similar al que hace JavaScript implícito. Mira la lista de sobrecargas y luego intenta llamar a la función con los parámetros dados con la primera sobrecarga, si hay una coincidencia, elegirá esa sobrecarga como verdadera. 

Por ello, es habitual ordenar las sobrecargas desde las más específicas y detalladas hasta las menos precisas. Tenga en cuenta que la pieza function pickCard(x): any no forma parte de la lista de sobrecargas, por lo que la función solo tiene dos sobrecargas: una que toma un objeto y la otra que toma un número. Llamar a pickCard cualquier otro tipo de parámetro producirá un error.


Recursos


Deja un comentario