Tipos Avanzados en Typescript
Tags: typescript, javascript
Published: 2020-04-22 04:37:39

## Protectores de tipo y tipos diferenciadores

Una expresión común en JavaScript para diferenciar entre dos valores posibles es verificar la presencia de un miembro. Como mencionamos, solo puede acceder a miembros que están garantizados en todos los componentes de un tipo de unión.

~~~
let pet = getSmallPet();

// Cada uno de estos accesos a la propiedad causará un error
if (pet.swim) {
  pet.swim();
} else if (pet.fly) {
  pet.fly();
}
~~~

Para que funcione el mismo código, necesitaremos usar una aserción de tipo:

~~~
let pet = getSmallPet();

if ((pet as Fish).swim) {
  (pet as Fish).swim();
} else if ((pet as Bird).fly) {
  (pet as Bird).fly();
}
~~~

## Usando el operador in
El operador in ahora actúa como una expresión de reducción para los tipos.

Para una ***n*** in expresión ***x***, donde ***n*** es un literal de cadena o tipo de literal de cadena y ***x*** es un tipo de unión, la rama "verdadera" se estrecha a los tipos que tienen una propiedad opcional o requerida ***n***, y la rama "falsa" se estrecha a los tipos que tienen una opción o faltan propiedad ***n***.

~~~
function move(pet: Fish | Bird) {
  if ("swim" in pet) {
    return pet.swim();
  }
  return pet.fly();
}
~~~

## typeof type guards

~~~
function isNumber(x: any): x is number {
  return typeof x === "number";
}

function isString(x: any): x is string {
  return typeof x === "string";
}

function padLeft(value: string, padding: string | number) {
  if (isNumber(padding)) {
    return Array(padding + 1).join(" ") + value;
  }
  if (isString(padding)) {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}
~~~

Sin embargo, tener que definir una función para determinar si un tipo es primitivo es una molestia. Afortunadamente, no necesita abstraer `typeof x === "number"` en su propia función porque TypeScript lo reconocerá como un protector de tipo por sí solo. Eso significa que podríamos escribir estos cheques en línea.

~~~
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
  throw new Error(`Se espera un string o number, no '${padding}'.`);
}
~~~

## Tipos Anulables

Efectivamente, `null` y `undefined` son valores válidos de cada tipo. Eso significa que no es posible evitar que se asignen un tipo `null`, incluso cuando desee evitarlo. El inventor de null, Tony Hoare, llama a esto su "error de mil millones de dólares".

~~~
let s = "foo";
s = null; // error, 'null' no esta asignado para 'string'
let sn: string | null = "bar";
sn = null; // ok

sn = undefined; // error, 'undefined' no esta asignado para 'string | null'
~~~

## Parámetros y propiedades opcionales

Con `--strictNullChecks`, un parámetro opcional agrega automáticamente | undefined:

~~~
function f(x: number, y?: number) {
  return x + (y || 0);
}
f(1, 2);
f(1);
f(1, undefined);
f(1, null); // error, 'null' no es asignable a 'number | undefined'
~~~

Lo mismo es cierto para las propiedades opcionales:

~~~
class C {
  a: number;
  b?: number;
}
let c = new C();
c.a = 12;
c.a = undefined; // error, 'undefined' no es asignable a 'number'
c.b = 13;
c.b = undefined; // ok
c.b = null; // error, 'null' no es asignable a 'number | undefined'
~~~

## Escribir guardias y aserciones

Dado que los tipos anulables se implementan con una unión, debe usar un protector de tipo para deshacerse de él null. Afortunadamente, este es el mismo código que escribirías en JavaScript:

~~~
function f(sn: string | null): string {
  if (sn == null) {
    return "default";
  } else {
    return sn;
  }
}
~~~

La eliminación de `null` es bastante obvia aquí, pero también puede usar operadores `terser`:

~~~
function f(sn: string | null): string {
  return sn || "default";
}
~~~

## Type Aliases

Los tipos Aliases son similares a las interfaces pero pueden nombrar tipos primitivos, tuplas y cualquier otro tipo que se tendria que escribir a mano

~~~
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

function getName(n: NameOrResolver): Name {
  if (typeof n === "string") {
    return n;
  } else {
    return n();
  }
}
~~~

También podemos hacer que un tipo Aliases se refiera a sí mismo en una propiedad:

type Tree<T> = {
  value: T;
  left: Tree<T>;
  right: Tree<T>;
};

Junto con los tipos de intersección, podemos hacer algunos tipos bastante alucinantes:

~~~
type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
  name: string;
}

var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
~~~