Tipado estático

<< Configurar entorno Orientación a objetos >>

Variables y constantes

Tipado implícito

Las variables admiten tipado implícito y explícito. El tipado implícito es el que se aplica por defecto cuando creamos una variable (o constante) y le asignamos un valor. La diferencia con JavaScript, es que el tipado es estático, es decir, no se puede reasignar a esta varible a un dato de otro tipo. Es equivalente a utilizar var para declarar una variable en lenguajes como Java o C#.

let personName = "John"; // Tipo implícito -> string
personName = 34; // ERROR: Type 'number' is not assignable to type 'string'.

Si se declara una variable sin especificar el valor ni el tipo, tomará el tipo especial any. Las variables con este tipo se comportan como variables JavaScript, es decir, con tipado dinámico. No es una forma recomendada de trabajar en TypeScript. Para eso, sería mejor trabajar directamente con JavaScript.

let a; // Tipo implícito any
a = 34;
a = "Hello";
console.log(a);

Muchas veces, el tipado implícito no es suficiente. Por ejemplo, si queremos asignar a una variable un array vacío, TypeScript no podrá inferir qué tipo de valores almacenará el array. Sabrá que es un array pero lo tratará como un array de any, es deicr, nos permitirá almacenar cualquier tipo de valor.

const a = []; // tipo any[]
a[0] = 23;
a[1] = "hello";
console.log(a);

Tipado explícito

Con el tipado explícito podemos indicar a TypeScript el tipo de valor que puede referenciar una variable o constante. Para ello usamos la sintaxis nombreVariable: tipo. Cuando TypeScript puede inferir correctamente el tipo de una variable, es innecesario hacerlo de manera explícita, pero no pasa nada si se hace.

let a: number;
a = 23;
a = "Hola"; // ERROR: Type 'string' is not assignable to type 'number'

Como tipos primitivos podemos usar number, string, o boolean.

Arrays

Los arrays se pueden tipar de 2 maneras para indicar el tipo de datos que pueden contener. El primero es utilizando los corchetes junto al tipo (ej: number[]), y el segundo sería como instancias de la clase Array (ej: Array<number>).

const a: number[] = [];
a[0] = 24; // Ok
a[1] = 35; // Ok
a[2] = "34"; // Error: Type 'string' is not assignable to type 'number'
a.push("Hola");  // Error: Argument of type 'string' is not assignable to parameter of type 'number'

const a2: Array<string> = [];
a2.push("Hola"); // Ok

Funciones

Es muy recomendable tipar siempre los parámetros de las funciones. De esta forma habilitamos 2 cosas:

  • A la función no le podremos pasar valores del tipo incorrecto, lo que nos ahorrará múltiples comprobaciones.
  • El editor nos habilitará el autocompletado de código dentro de la función. Por fin sabrá qué tipo de valor va a recibir cada parámetro.
function suma(n1: number, n2: number) {
    return n1 + n2;
}

console.log(suma(3, 5)); // 8
console.log(suma(3, "5")); // ERROR: Argument of type 'string' is not assignable to parameter of type 'number'

El tipo de dato que devuelve una función se puede inferir directamente a partir del tipo de dato que devuelve la instrucción return (void si no devuelve nada). Sin embargo, es recomendable indicarlo por varios motivos:

  • Si la funcioń tiene que devolver algo, nos obligará a hacerlo. Incluso si hay varios caminos en el código (if..else por ejemplo), controlará que en todas las situaciones la función devuelva siempre un dato del tipo indicado.
  • Si el tipo devuelto es void, impedirá que devolvamos algo.
  • A nivel de documentación del código, es más fácil entender lo que devuelve la función.
function suma(n1: number, n2: number): number {
    return n1 + n2;
}

function saluda(): void {
    console.log("Hola");
}

Tipo función

Para tipar una variable o un parámetro como tipo función, debemos utilizar un formato de función flecha a la hora de establecer el tipo de los parámetros y del return. De esta manera nos aseguramos que no podemos asignar una función que no cumpla las características establecidas.

// Recibe 2 parámetros de tipo number y devuelve un number
let  f: (n1: number, n2: number) => number;
// No hace falta tipar parámetros o lo que devuelve al declararla
f = (n1, n2) => n1 + n2;
console.log(f(3, 5)); // 8
f = (n1, n2) => n1 - n2;
console.log(f(3, 5)); // -2

function operar(n1: number, n2: number, f: (n1: number, n2: number) => number): number {
    return f(n1, n2);
}

console.log(operar(3, 5, (n1, n2) => n1 * n2));

Unión de tipos

A la hora de establecer un tipado estático, se pueden combinar varios tipos separados por '|'. Esto permite que el valor asociado pueda ser de cualquiera de esos tipos. Tiene la desventaja de que el autocompletado solo funcionará para los atributos y métodos comunes de dichos tipos.

function longitud(cifra: number | string): number {
    return String(cifra).length;
}

console.log(longitud(345)); // 3
console.log(longitud("6546")); // 4

Crear nuevos tipos

Cuando el tipado es largo, por ejemplo para definir una función, o para una unión de tipos. Se puede crear un nuevo tipo, que funciona como alias para ese tipado más extenso.

// Se puede hacer también una unión de valores literales
type Rol = "admin" | "usuario" | "invitado";

class Persona {
    nombre: string;
    rol: Rol; // Solo puede ser "admin", "usuario" o "invitado"

    constructor(nombre: string, rol: Rol) {
        this.nombre = nombre;
        this.rol = rol;
    }
}

let p = new Persona("Juan", "admin"); // OK
p = new Persona("Pepe", "mago"); // ERROR: Argument of type '"mago"' is not assignable to parameter of type 'Rol'

Tuplas

Las tuplas son arrays de longitud fija y tipos definidos para cada posición del mismo. Los tipos de cada posición del array se establecen entre corchetes y separados por comas.

type TuplaPersona = [string, number];

const p: TuplaPersona = ["Pepe", 23]; // OK
const p: TuplaPersona = [45, 23]; // ERROR: Type 'number' is not assignable to type 'string'

Tipado de objetos

Para tipar algo como un objeto, podemos utilizar la clase a la que pertenece el objeto:

class Persona {
    nombre: string;
    edad: number;

    constructor(nombre: string, edad: number) {
        this.nombre = nombre;
        this.edad = edad;
    }
}

const p: Persona = new Persona("Juan", 54);

También podemos definir una estructura JSON para tipar las propiedades que debe tener el objeto. Sirve tanto para objetos creados a partir de una clase como para objetos genéricos JSON:

// Array de personas
const personas: { nombre: string, edad: number }[] = [
    {
        nombre: "Ana",
        edad: 34
    },
    {
        nombre: "Juan",
        edad: 35
    }
];
console.log(personas);

Para hacer un código más limpio podemos crear un tipo con dicha estructura o utilizar una interfaz. Es más o menos equivalente a utilizar una interfaz para ello, pero la interfaz permite más flexibilidad (implementarla en una clase, crear interfaces derivadas). Esto último lo veremos en la siguiente sección.

interface Persona {
    nombre: string;
    edad: number;
}

// Array de personas
const personas: Persona[] = [
    {
        nombre: "Ana",
        edad: 34
    },
    {
        nombre: "Juan",
        edad: 35
    }
];
console.log(personas);

<< Configurar entorno Orientación a objetos >>