1. Libros y videos
  2. JavaScript
  3. TypeScript
Extrait - JavaScript Desarrolle eficazmente (2ª edición)
Extractos del libro
JavaScript Desarrolle eficazmente (2ª edición) Volver a la página de compra del libro

TypeScript

Introducción

1. Objetivos

JavaScript es especialmente versátil, como hemos podido ver, pero requiere un esfuerzo de programación para encontrar los usos adecuados. La empresa Microsoft ha desarrollado un superconjunto de JavaScript llamado TypeScript. Cuando hablamos de superconjunto, nos referimos a que JavaScript está incluido en TypeScript, que añade lo que parece faltarle al lenguaje, como el tipado de datos. Una de sus ventajas es la etapa de compilación de TypeScript, que consiste en obtener un código JavaScript compatible con los navegadores.

2. Hello world

Partiremos de la forma más sencilla en JavaScript para realizar nuestro primer ejemplo. La idea es obtener el compilador TypeScript a través del gestor de paquetes de Node.js (NPM).

Si no dispone de la utilidad NPM, puede descargarla en la página web de nodejs.org (https://nodejs.org/es/download/).

El siguiente comando sirve para instalar el compilador TypeScript:

npm install -g typescript  
images/JS-23.png

Ahora vamos a crear un archivo TypeScript (extensión .ts) utilizando únicamente JavaScript por ahora.

ejemplo1.ts

function hello() {  
  alert( "hello world" );  
}  
hello();  

Para compilarlo, simplemente utilizamos el comando:

tsc ejemplo1.ts  

Esto genera un archivo ejemplo1.js. Este archivo es estrictamente equivalente a ejemplo1.ts, ya que por el momento solo...

Tipado de expresiones

1. Tipos disponibles

TypeScript retoma la misma sintaxis que ya hemos usado con variables y constantes. Lo que cambia es la necesidad de indicar un tipo al declarar una variable. Si utiliza Visual Studio Code, le mostrará todos los errores de tipo a medida que escriba.

Estos son los tipos primitivos disponibles:

  • boolean: un valor booleano.

  • number: un número de coma flotante.

  • bigint: un número entero.

  • string: una cadena de caracteres.

A esto se suman tipos especiales, como:

  • []: asociado a un tipo para designar un array.

  • tupla: para designar un array con tipos en un índice determinado.

  • enum: enumeración de valores asociada automáticamente a un entero.

  • unknown: para un tipo desconocido.

  • any: para indicar a TypeScript que ignore el tipo.

  • void: designa una función que no devuelve ningún valor.

  • undefined: para un valor no asignado.

  • null: para la ausencia de una referencia a un objeto.

  • never: designa una función que no termina o provoca una excepción.

  • object: designa un objeto.

2. Declaraciones

El tipo de un valor se especifica mediante «:» seguido del nombre del tipo en cuestión. Por ejemplo:

let apellido: string = "Brines";  
let nombre: string = "Alejandro";  
const pi: number = 3.14;  
const pis: number[] = [ pi, 2*pi , 3*pi ];  
   
type Persona = {  
    apellido: string,  
    nombre: string  
};  
   
const miObjeto: Persona = {  
    apellido: apellido,  
    nombre: nombre  
};  
  
console.log( `Hola ${miObjeto.nombre} ${miObjeto.apellido}`  );  

En nuestro ejemplo, nombre y apellido son cadenas de caracteres (string). pis es un array de números. Para un objeto literal, hay que definir el tipo de cada propiedad. Para ello, hemos creado un tipo Persona antes de asignar los valores.

Lo mismo se aplica al tipado de una función con sus parámetros y, en su caso, su valor de retorno.

function contador( inicio: number, fin: number ): void {  
    for ( let valor: number = inicio; valor <= fin; valor ++ ) {  
        console.log(valor);  
    }  
}  ...

Clases

1. Declaración y uso

El concepto de clase no dista mucho de lo que ya hemos visto en JavaScript «tradicional». Principalmente, añadiremos los conceptos de tipado relativos a los atributos y los métodos.

class Persona {  
    apellido:string;  
    nombre:string;  
  
    constructor( apellido:string, nombre:string ) {  
        this.apellido = apellido;  
        this.nombre = nombre;  
    }  
   
    hola():void {  
        console.log( `Hola ${this.apellido} ${this.nombre}` );  
    }  
}  
const autor: Persona = new Persona( "Brines", "Alejandro" );  
   
autor.hola(); // «Hola Brines Alejandro»  

Cabe destacar que la sintaxis es más estricta que en JavaScript tradicional, ya que dejar atributos sin inicializar en el constructor se considera un error.

Curiosamente, solo puede haber un único constructor, aunque la definición de dicho constructor puede ser múltiple. Al igual que con los argumentos de una función, podemos declarar un atributo opcional con el operador «?».

class Persona {  
    apellido:string;  
    nombre:string;  
    edad?:number;  
  
    constructor( apellido:string, nombre:string );  
    constructor( apellido:string, nombre:string, edad:number );  
    constructor( apellido:string, nombre:string, edad?:number ) { 
        this.apellido = apellido;  
        this.nombre = nombre;  
        if (edad !== undefined)  
            this.edad = edad;  
    }  
  
    hola():void {  
        console.log( `Hola ${this.apellido} ${this.nombre}  
${this.edad ?? "??"}` );  
    }  
}  
   ...

Interfaces

1. Declaración

JavaScript, al igual que TypeScript, no permite la herencia múltiple, lo que significa que una clase solo puede tener un único padre. Se trata de una decisión de los diseñadores del lenguaje para evitar cualquier ambigüedad, ya que podrían existir propiedades, atributos o métodos con el mismo nombre en dos clases padre diferentes.

Sin embargo, puede ocurrir que necesitemos gestionar objetos que no tienen necesariamente una relación de herencia evidente o posible. En este caso, intentaremos manipular todos estos objetos mediante una especie de contrato que denominamos interfaz.

Se trata de un concepto que se encuentra en otros lenguajes, como Java o C#. La interfaz designa las firmas que se espera encontrar en un conjunto de clases. Las clases no necesitan estar relacionadas entre sí; solo deben respetar la interfaz y garantizar que ciertas firmas de métodos estén presentes.

Su uso no siempre resulta evidente, pero en la práctica ayuda a simplificar el código. Imagine, por ejemplo, que tiene un servicio que empaqueta en cajas todo tipo de objetos. Estos objetos deben tener un tamaño determinado, pero pueden no tener ninguna relación entre sí.

Gracias a una interfaz, es posible «embalarlos» sin conocer su verdadera naturaleza. Sin embargo, estos objetos deben tener al menos una dimensión para facilitar la operación...

Genéricos

1. Declaración en una función

Hasta ahora, hemos utilizado funciones o clases con argumentos o miembros de un tipo muy concreto. Esto significa que no podemos reutilizar estas funciones y clases fuera de estos tipos. Sin embargo, hay casos en los que nos gustaría poder manipular nuestras funciones/clases con tipos diferentes. Esto es lo que nos permiten hacer los «genéricos».

Es un principio que se encuentra a menudo en otros lenguajes, como Java, C# o C++.

Vamos a empezar por escribir una función genérica; el principio consiste en ignorar uno o varios tipos y especificarlos como una forma de parámetro entre los signos mayor y menor.

function maxVal<T>(args: T[], comparador: (a: T, b: T) => number): T { 
    return args.reduce(  
        (acumulador: T, valor: T) =>  
            comparador(acumulador, valor) > 0 ? acumulador :  
valor );  
   
}  
   
const miArray = [1,2,3,4,5,6];  
   
console.log( maxVal(miArray, (a,b)=> a - b ) );  
  
const miArray2 = ["aaaaa", "zzzz"];  
   
console.log( maxVal( miArray2, (a,b)=>a.localeCompare(b) ) )  

En este ejemplo, tenemos una función genérica maxVal que toma como argumento un array y devuelve su valor más grande, independientemente del tipo utilizado. Pero para que esta función pueda utilizar...

Espacio de nombres

Declaración y uso

El espacio de nombres, definido con la palabra clave namespace, «alberga» todas las variables/constantes, funciones, interfaces y clases que tengan una agrupación lógica. Las declaraciones incluidas en él permanecen locales, lo que limita los posibles conflictos de nombres: un mismo identificador puede repetirse en espacios de nombres diferentes sin causar problemas.

Para que una declaración sea accesible fuera del espacio de nombres, se le antepone la palabra clave «export».

// Espacio de nombres  
 
  namespace Libros {  
    class Libro {  
        private titulo:string;  
        public get Titulo() {  
            return this.titulo;  
        }  
        constructor(titulo:string) {  
            this.titulo = titulo;  
        }  
    }  
   export class Autor {  
        apellido:string;  
        nombre:string;  
       ...

Conclusión

TypeScript elimina el exceso de flexibilidad que constituye tanto una fortaleza (simplicidad, libertad sintáctica, etc.) como una debilidad de JavaScript (evolución, uso compartido de código, etc.). La incorporación del tipado de datos y las posibilidades relacionadas con los objetos sitúan a JavaScript al mismo nivel que los lenguajes orientados a objetos recientes. Sin embargo, no parece necesariamente adecuado para todos los proyectos. Se puede considerar que TypeScript es más apropiado para proyectos de gran envergadura con importantes restricciones en términos de desarrollo o para la creación de frameworks. Por otra parte, el lenguaje JavaScript sigue evolucionando y muchas de las innovaciones integradas en TypeScript, como las clases, se han incorporado desde entonces al lenguaje tradicional, lo que hace que el uso de TypeScript resulte cada vez menos evidente.