El uso básico de TypeScript conlleva trabajar con los tipos. TypeScript tiene más tipos que JavaScript, y además puedes crear tus propios tipos. De modo que lo primero que debemos aprender son los tipos básicos en TypeScript.
Como sabes, JavaScript utiliza tipos dinámicos, que se resuelven en tiempo de ejecución. Esto nos ofrece más flexibilidad como desarrolladores, ya que podemos cambiar el tipo de datos guardados en una variable sobre la marcha. Pero también es una práctica sujeta a errores, que TypeScript corrige ofreciendo tipos estáticos, que se configuran durante la etapa de desarrollo.
De este modo anticipamos los posibles errores y obtenemos un código más limpio para la etapa de producción. Por tanto, TypeScript nos ofrece una ayuda valiosa… pero solo durante la etapa de desarrollo.
Tipos básicos en TypeScript
Los tipos básicos en TypeScript son:
- number. No hay conceptos diferenciados para números enteros y fracciones, como ocurre en JavaScript. Todo son números.
- string. TS da soporte a las cadenas de texto, empleando tanto la notación de comillas simples como la de comillas dobles y acentos invertidos.
- boolean. TypeScript admite los valores true y false, pero no los truthy y falsy propios de JavaScript.
- object. TS también soporta los objetos, aunque tiene tipos más específicos que JavaScript para ellos.
- array. Otro de los elementos comunes entre JS y TS son las listas o arreglos.
- tuple. Las tuplas son arrays de longitud y tipos determinados. Se trata de un tipo que existe en TS pero no en JS.
- enum. Otro tipo no soportado en JS (pero existente en otros lenguajes) que permite almacenar identificadores de constantes.
- any. Permite almacenar cualquier tipo de dato en la variable. Aporta la flexibilidad de JavaScript, pero nos hace perder las ventajas de las restricciones de tipos de TS.
- union. Permiten determinar más de un tipo admitido en una misma variable, utilizando la pleca (|).
- literal. Con los literales no solo declaramos el tipo de la variable, sino también su valor. Funcionan de modo similar a las uniones.
- function. Como en otros lenguajes de programación, las funciones pueden tener un valor de retorno. En caso de no retornar nada, su valor debería ser “void” (desconocido para JS, que utiliza “undefined”, pero común en otros lenguajes).
- unknown. El tipo unknown nos permite atribuir cualquier valor a la variable. Pero, al contrario de lo que ocurriría con any, no podremos reasignar esta variable como valor de otra variable sin comprobar previamente que los tipos de ambas coinciden. Se trata, por tanto, de una variable más restrictiva del tipo any.
- never. Hay funciones que detienen la ejecución del código. Por ejemplo, porque lo interrumpen o porque entran en un bucle infinito. En estos casos ni siquiera tiene sentido el concepto de “valor de retorno”, porque la función nunca terminará. Por tanto, debemos utilizar el valor never.
[codepen_embed height="300" default_tab="css,result" slug_hash="VwQPQdp" preview="true" editable="true" user="agarzon48"]See the Pen
TypeScript 1.1. - Type declarations by Adrián Garzón (@agarzon48)
on CodePen.[/codepen_embed]
Sintaxis básica de TypeScript
Cuando queremos asignar un tipo a una variable, lo hacemos escribiendo dos puntos seguidos del tipo de dato:
function add(n1: number, n2: number) {
return n1 + n2;
}
De este modo, TS no nos permitirá compilar un código donde detecte alguna inconsistencia entre nuestros tipos.
“cannot redeclare block-scoped variable”
Los IDE modernos traen soporte para escribir TS, aportando útiles consejos. Pero hay una advertencia común que te dirá algo así como “cannot redeclare block-scoped variable”. Si te encuentras con ella, probablemente tengas ambos archivos (el .ts y el .js ya compilado) abiertos simultáneamente. Solo tienes que cerrar tu archivo .js y el error desaparecerá.
Otros errores frecuentes
Recuerda que los tipos primitivos en TypeScript se escriben con letras minúsculas. De otro modo, el compilador no entenderá tu código.
La inferencia de tipos
El compilador es lo suficientemente “inteligente” como para inferir o deducir de qué tipo es una variable ya inicializada. De modo que no será necesario que la declares.
Cuando no estemos inicializando la variable al declararla sí debemos asignarle un tipo para evitar que TypeScript la infiera como de tipo “any”.
// We don't need to specify the type of the parameters, because we can infer it from the value (type inference).
const someNumber = 5;
// But we can constraint a variable to a specific type if we want to avoid latter errors when assigning it a value (explicit assignment).
let anotherNumber: number;
// So, this will work:
anotherNumber = 5;
// But this will not:
anotherNumber = "5";
Declarando objetos en TypeScript
Al declarar un objeto en TypeScript podemos ser bastante específicos, indicando al compilador qué propiedades tenemos dentro del objeto y cuál es el tipo de cada una de ellas. Sin embargo, dado que contamos con la ayuda de la inferencia de tipos suele ser más recomendable dejar que el compilador averigüe por sí mismo las propiedades y sus valores para evitar ser redundante.
De modo que, aunque podemos ser muy expresivos al declarar un tipo object en TypeScript, lo mejor es evitar la redundancia e inicializar el objeto como lo haríamos en JS. Cuando sí puede ser útil la declaración de tipos es cuando declaramos un objeto pero no le atribuimos un valor inicial.
Declarando arrays en TypeScript
Al declarar una array en TypeScript, debemos indicar el tipo de los elementos que almacenaremos en ella.
let languages: string[];
El problema es que este enfoque nos restringe a utilizar un único tipo de dato. Una solución para almacenar diferentes tipos de datos en la misma lista es declararla como de tipo “any”:
let languages: any[];
De todos modos, recuerda que esto nos hace perder las ventajas de tipado propias de TS.
Declarando tuplas en TypeScript
Para declarar una tupla en TypeScript debemos escribir el tipo de dato que va a tener cada índice entre corchetes, de forma similar a lo que haríamos al declarar un array en JavaScript:
let userWithId: [string, number];
Las tuplas nos permiten utilizar arrays más limitadas durante el desarrollo. Pero ten en cuenta que el método push() todavía funcionará, lo que podría romper la estructura declarada de la tupla.
Declarando enums en TypeScript
enum es un tipo de TS que nos permite almacenar constantes numéricas de forma cómoda. En JS y otros lenguajes de programación, es una práctica frecuente almacenar constantes al principio del archivo para que el IDE nos proporcione un mejor soporte y escribir estructuras de control sea más limpio y sencillo.
TS nos proporciona un modo de declarar todas estas constantes en un enum, al que pasaremos una lista de etiquetas:
enum UserRole { USER, ADMIN, SUPER_ADMIN };
A partir de este momento, TS asignará a cada etiqueta un número correlativo, empezando desde cero. Por supuesto, se puede sobreescribir el número inicial estableciendo otro en la primera etiqueta:
enum UserRole { USER = 5, ADMIN, SUPER_ADMIN };
O incluso atribuir otros valores (no necesariamente de tipo número) a cada uno de los elementos del enum:
enum UserRole { USER = 5, ADMIN = 10, SUPER_ADMIN = 15 };
Si no has trabajado antes con enums puede resultarte complejo entender el concepto ahora mismo, pero incluiré un fragmento de código en el artículo para que puedas entenderlo mejor.
Utilizando el tipo any en TypeScript
¡Recuerda! Si podemos evitarlo, no deberíamos utilizar el tipo any en TypeScript. Nos quita las ventajas del tipado severo y pasamos a utilizar un sistema más similar al del JavaScript de toda la vida.
Declarando unions en TypeScript
Las uniones nos ofrecen más flexibilidad, permitiéndonos admitir diferentes tipos predeterminados sin perder la constricción de tipos. Para declarar una unión podemos separar varios tipos por una pleca, que es este símbolo: |.
Ten en cuenta que si utilizas unions en parámetros de funciones, es posible que debas comprobar cuál de los tipos de dato tienen los parámetros para ofrecer una lógica diferente para cada uno de ellos.
Declarando literals en TypeScript
Al utilizar un literal estamos especificando al compilador tanto el tipo de dato como su valor. Se puede utilizar como alternativa a los enums, donde no establecemos un array numerado sino uno de nombres literales. Para escribirlos podemos utilizar la notación de las unions:
let fileExtension: ".png" | ".jpg";
Declarando funciones en TypeScript
Como ya hemos visto, los parámetros en las funciones se pueden tipar como cualquier otra variable. A fin de cuentas, eso es exactamente lo que son.
Pero la propia función puede tener también un valor de retorno, lo que nos permite predefinirlo con TypeScript. Para hacerlo solo debemos incluir dos puntos y el tipo tras la lista de argumentos:
function uppercaseUserName(userName: string): string {
return userName.toUpperCase();
}
De todos modos, suele ser una buena práctica dejar que el compilador infiera el tipo del retorno de la función.
Cuando una función no tiene un valor de retorno le podemos asignar el valor “void”. Esto sonará familiar a quien venga de otros lenguajes, pero puede ser raro para quien viene de JavaScript, porque:
- En JavaScript no existe el tipo “void”.
- En lugar de void, una función sin valor de retorno retorna “undefined”.
- Pero “undefined” es un valor conocido por TypeScript, por lo que necesariamente habrá que utilizar “void”.
Aquí va un ejemplo:
function cheerMe(): void {
console.log("Well done!");
}
Lo último a destacar en este apartado es que las funciones también pueden ser un tipo de dato:
// A function as a data type
// First, let's create a dummy function
function combineTwoStrings (string1: string, string2: string) {
return string1 + string2;
}
// Now, let's create its placeholder
let doYourThing: Function;
// We can be more explicit:
let doTheCombination: () => string;
// Or even more:
let doCombineTwoStrings: (a: string, b: string) => string;
Incluso podríamos anidar funciones dentro de la lista de parámetros de otra función (por ejemplo, cuando vayamos a usar un callback). Eso sí, ten en cuenta que TypeScript tiene un límite aquí: aunque definas “void” como el valor de retorno de una función de callback, siempre podrás dar otro valor de retorno sin que el compilador se queje.
Más sobre los tipos en TypeScript
Esta clase ha sido muy larga, así que solo quiero tocar otros tres conceptos importantes para darla por cerrada:
- El tipo “never”, que se aplica a aquellas funciones que no tienen valor de retorno posible, ya que representan un bucle infinito o una interrupción de la ejecución.
- El tipo “unknown”, que nos ofrece un tipo flexible, aunque más restringido que “any”.
- Y los alias de tipos o tipos personalizados, que nos permiten escribir código más organizado, limpio y reutilizable al declarar tipos repetitivos o complejos.
[codepen_embed height="300" default_tab="css,result" slug_hash="XWZpPwo" preview="true" editable="true" user="agarzon48"]See the Pen
TypeScript 1.2.- More on basic TypeScript by Adrián Garzón (@agarzon48)
on CodePen.[/codepen_embed]
Tipo never
El tipo never solo se utiliza cuando una función no va a tener valor de retorno. Esto puede sonar extraño, tras haber visto el tipo void. Pero lo cierto es que podemos utilizar funciones que cumplirán con su misión sin devolver ningún valor:
function cheerMe(): void {
console.log("Well done!");
}
O utilizar funciones cuya misión será, precisamente, evitar que el código siga ejecutándose:
function throwCustomError(message: string, code: number): never {
throw {
message: message,
code: code
};
}
Ante este último escenario, “never” nos permite ser muy explícitos, indicando al compilador y a otros desarrolladores que, si este código se ejecuta, debería evitar que se ejecute nada más.
Tipo unknown
El tipo unknown funciona de forma parecida a any, ya que nos permite introducir cualquier tipo de dato en la variable. Sin embargo, hay una diferencia radical:
Si yo atribuyo un valor a una variable de tipo any, podre utilizarla como valor de otras variables, aunque estén tipadas. Pero si lo hago en una variable de tipo unknown, no podré asignarla como valor de otra variable tipada hasta que no compruebe que sus tipos coinciden. En el ejemplo que he subido en el fragmento de código lo verás más claro.
Ten en cuenta que esto hace unknown más restrictivo que any, y por tanto deberíamos utilizar este tipo antes que any.
Alias o tipos personalizados
Hemos hablado de los tipos union y literal, que nos obligan a escribir dos o más tipos separados por una pleca. Ahora conviene introducir un nuevo concepto, que son los alias o tipos personalizados (aliases o custom types).
Declarar un tipo personalizado nos ayudará a mantener un código más limpio y reutilizable. Para ello solo tenemos que declarar el nuevo tipo con la keyword “type”:
type AllowedTypes = string | number | boolean;
Podemos utilizar cualquier nombre para nuestro nuevo tipo, siempre que no se trate de un término reservado por JavaScript o TypeScript.
La ventaja de declarar tipos personalizados es que ahora podremos utilizarlos en cualquier sitio que necesitemos. Además, su uso no se limita a unions o literals, sino que podemos crear tipos mucho más complejos.
Nótese que, por convención, escribimos en mayúscula la primera letra de nuestros tipos personalizados.
No te pierdas...
- ¿Qué es TypeScript?
- TypeScript básico.
- Documentación oficial.