Bruno R
Sintaxis. Como definir tipos basando para el ejemplo los tipos primitivos, string, number, boolean. (Mas adelante se podran crear tipos personalizados)
Para especificar el tipo de una matriz de numeros ([1, 2, 3]), puede usar la sintaxis number[]; esta sintaxis funciona para cualquier tipo (ej: string[] es una matriz de cadenas).
Tambien se puede escribir Array<number>, Array<string>.
TypeScript tambien tiene un tipo especial de tipo de dato, any, que puede usar siempre que no desee que un valor en particlar cause errores de verificacion de tipos
Los tipo any son utilizados cuando no desea escribir un tipo de dato largo solo para convencer a TS que una linea de codigo es particular esta bien
Cuando no especifica un tipo, y TS no puede deducirlo del contexto, el compilador normalmente utilizara de manera predeterminada any.
Pero por lo general se querra evitar esto, porque any no tiene un verificador de tipo. Usar la bandera del compilador noImplicitAny para marcar cualquier any como un error.
(seguro tendre una configuracion en ts.config.json)
Al declarar una variable opcionalmente puede agregar una anotacion de tipo, para especificar el tipo de variable:
let myName: string = "Bruno";
En la mayoria de los casos, declarar el tipo de dato no es necesario, ya que TS intenta inferir automaticamente los tipos que se usan en el codigo.
TypeScript permite especificar los tipos de los valores de entrada y salida de las funciones.
Al igual que siempre, la declaracion del tipo va despues:
// Anotacion de
tipo
string en el parametro name function greet(name: string) { console.log("hola, " + name.toUpperCase() + "!!"); }
Los tipos de retorno en una funcion se colocan despues de los parametros:
function getFavoriteNumber(name: string): number { return Number(name); }
Las funciones anonimas se definen distinto a las declaraciones de funciones. Cuando TS determina como sera llamada, los parametros de esta reciben automaticamente tipos.
// No hay anotaciones de tipo, pero TS puede detectar errores const names = ["Alice", "Bob", "Eve"]; // Tipado contextual para la funcion names.forEach(function (s) { console.log(s.
toUppercase
()); // Error: Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'? }); // El tipado contextual tambien funciona para las funciones de flecha names.forEach((s) => { console.log(s.
toUppercase
()); // Error: Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'? });
Para entender el contexto y lograr un tipado al parametro s TS uso los tipos del foreach, junto con el tipo inferido de la matriz.
Este proceso se denomina tipificacion contextual, porque el contexto en el que se produjo la funcion informa que tipo debe tener.
Aparte de los primitivos, el tipo mas comun es el tipo de objeto. Esto se refiere a cualquier valor de JS con propiedades, (que serian casi todas). Para definir un tipo de objeto (object type), simplemente enumeramos sus propiedades y sus tipos
Ejempo de una funciona que toma un objeto similar a un punto:
// La anotación de tipo del parámetro es un object type. function printCoord(pt: { x: number; y: number }) { console.log("The coordinate's x value is " + pt.x); console.log("The coordinate's y value is " + pt.y); } printCoord({ x: 3, y: 7 });
En el ejemplo se uso como parametro un object type con dos propiedades: x e y (ambos de tipo number).
Se puede usar , (coma simple) o ; (punto y coma), para separar las propiedades, y el ultimo separador es opcional.
Los tipos de datos agregador en las propiedades tambien son opcionales. Si no los definimos se usara por defecto el tipoany.
Los object type tambien pueden especificar que algunas o todas sus propiedades sean opcionales.
Para lograr esto se debe agregar un “?”, despues del nombre de la propiedad:
function printName(obj: { first: string; last?: string }) { // ... } // Ambas opciones estan bien printName({ first: "Bob" }); printName({ first: "Alice", last: "Alisson" });
En JS, si accede a una propiedad que no existe, se obtiene undefinded en vez de un error de tiempo de ejecucion.
Por esta razon cuando lea una propiedad opcional, se tendra que buscar undefined antes de usarlo.
function printName(obj: { first: string; last?: string }) { // Error - Podria fallar si no se proporciona 'obj.last' console.log(obj.last.toUpperCase()); //Object is possibly 'undefined'. if (obj.last !== undefined) { // Esto corrije de posibles errores como el de arriba console.log(obj.last.toUpperCase()); } //Para probar - Una alternativa segura que utiliza sintaxis moderna de JS: console.log(obj.last?.toUpperCase()); }
El sistema de tipos de TS permite crear nuevos tipos a partir de los existentes, utilizando una gran variedad de operadores.
La primer forma de combinar tipos es un tipo de union. Este es un tipo formado por dos o mas tipos de datos, por lo que el valor puede tener cualquiera de los tipos especificados en la union.
Cada entidad de estos tipos de datos unidos seria un miembro de la union
Ejemplo de funcion que puede tomar como parametro tanto number type como string type.
function printId(id: number | string) { console.log("Your ID is: " + id); } // OK printId(101); // OK printId("202"); // Error printId({ myID: 22342 }); // Argument of type '{ myID: number; }' is not assignable to parameter of
type 'string | number'
.
TS solo permite una operacion si esta es valida para todos los miembros de la union. Ejemplo, si tiene una union string | number, no podra usar metodos que solo esten disponibles en string pero si podra usar metodos que esten disponibles en ambos miembros.
function printId(id: number | string) { console.log(id.toUpperCase()); /*Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'.*/ }
La solucion a esto seria estrechar la union por medio del codigo.
function printId(id: number | string) { if (typeof id === "string") { // En esta rama, la identificaciĂłn es del tipo 'cadena' console.log(id.toUpperCase()); } else { // AquĂ, la identificaciĂłn es del tipo 'nĂşmero' console.log(id); } }
Ejemplo con array:
function welcomePeople(x: string[] | string) { if (Array.isArray(x)) { // Aca: 'x' es 'string[]' console.log("Hello, " + x.join(" and ")); } else { // Aca: 'x' es 'string' console.log("Welcome lone traveler " + x); } }
Como se dijo antes, en algunos casos, puede que todos los miembros tengan metodos en comun, por lo que no sera necesario bifurcar la funcion segun el tipo de dato recibido de la union.
// El tipo de retorno se infiere como number[] | string function getFirstThree(x: number[] | string) { return x.slice(0, 3); }
Anteriormente se uso object types y union types escribiendolos directamente en anotaciones de tipo. Pero es comun el querer usar el mismo tipo mas de una vez y referirse a el por un solo nombre, esto se puede lograr con los type alises.
type Point = { x: number; y: number; }; Â // Esto seria igual al
ejemplo anterior
function printCoord(pt: Point) { console.log("The coordinate's x value is " + pt.x); console.log("The coordinate's y value is " + pt.y); } Â printCoord({ x: 100, y: 100 });
Se puede utilizar un alias de tipo para dar nombre a cualquier tipo, no solo a tipos de objeto. Ejemplo, un alias de tipo puede nombrar un tipo de union (entonces cualquier tipo de dato definido puede ser nombrado)
type ID = number | string;
Una declaracion de interfaz es otra forma de nombrar un tipo de objeto
interface Point { x: number; y: number; } Â function printCoord(pt: Point) { console.log("The coordinate's x value is " + pt.x); console.log("The coordinate's y value is " + pt.y); } Â printCoord({ x: 100, y: 100 });
Al igual que cuando usamos alias de tipo, este codigo funciona como si hubieramos usado un tipo de objeto anonimo. TS solo se preocupa por la estructura del valor que le pasamos a printCoord, (osea que solo le importa que tenga las propiedades esperadas). Preocuparse solo por la estructura y las capacidades de los tipos es la razon por la que se llama a TypeScript un sistema de tipos estructuralmente tipado.
Los alias de tipo y las interfaces son muy similares y, en muchos casos se pueden elegir entre ambas libremente. Casi todas las caracteristicas de una interface estan disponibles en type, la diferencias clave esta en que los type no se pueden volver a abrir para agregar nuevas propiedadesm frente a las interface que siempre son extensibles.
Algunas otras caracteristicas:
A veces tendremos informacion sobre el tipo de un valor que TS no puede conocer.
Ejemplo, si utilizamos document.getElementById, TS solo sabe que esto devolvera algun tipo de HTMLElement, pero es posible que sepamos que en la pagina siempre tendremos un HTMLCanvasElement mediante una identificacion dada.
En estas situaciones se puede usar una asercion de tipo para especificar un tipo mas especifico:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
Al igual que una notacion de tipo, el comilador elimina las aserciones de tipo por lo que esto no afectara el comportamiento en el tiempo de ejecucion del codigo.
En este caso tambien se puede usar la sintaxis de parentesis angular (excepto si el codigo esta en un archivo .tsx):
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
TypeScript solo permite aserciones de tipo que se convierten en una version mas especifica o menos especifica de un tipo. Esta regla previene “imposibles” como:
const x = "hello" as number;
Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
A veces, esta regla puede ser demasiado conservadora (al buscar relaciones con los tipos) y no permitira coacciones complejas que podrian ser validas. Si esto sucede, se pueden usar dos aserciones, primero para llevarlo al tipo any (o unknown), luego al tipo deseado ⇒
const a = (expr as any) as T; //expr seria el valor del tipo de dato que queramos y lo estamos llevando a un tipo any // y luego lo pasamos a T que seria el tipo que queramos. // Obviamente todo esto con los errores que conllevaria si la utilizamos mal