En Typescript también se puede aplicar operaciones lógicas a los tipos, en concreto la de AND y la de OR. En otras palabras, podemos declarar tipos que sean con un tipo y otro, o tipos que sean de un tipo o de otro.

Estas uniones e intersecciones solo se pueden hacer con los Type alias en Typescript, ya que es una de las Diferencias entre tipos con nombre e interfaces en Typescript

Unión de tipos (OR)

Para hacer una unión de tipos en Typescript se usa el operador pipe, | (la barra vertical que se usa en Linux), ejemplo:

type RegisteredUser = { username: string };
type Visitor = { visitDate: Date };

type User = RegisteredUser | Visitor;

let user : User = { username: "jdoe" };
let visitor: User = { visitDate: new Date() };

Pero ojo porque esto tiene un problema. Imagina que creas una función que devuelve el tipo User que hemos creado antes. Typescript en este caso no nos deja acceder a las propiedades de ese objeto porque no sabe si es de tipo RegisteredUser o de tipo Visitor.

Aquí no nos queda más remedio que meter un if para saber si el objeto que devuelve la función tiene cierta propiedad para saber si es de un tipo o de otro.

Esto también se puede usar para el Tipado de funciones en Typescript, en concreto para los parámetros o para la salida de la función, ejemplo:

function add(value: number | string, value2: number | string) {
  return value + value2;
}

Como hemos dicho antes, si dentro de esta función queremos acceder a una propiedad de value que solo tiene lo strings, Typescript no te va a dejar porque no sabe si el valor es un string o un número. Para arreglarlo tenemos que meter el if que he mencionado antes, con un typeof para saber en tiempo de ejecución de qué tipo es:

function add(value: number | string, value2: number | string) {
  if (typeof value === "string") {
    console.log("Aquí sabemos que la variable es un string");
    console.log(value.toLowerCase());
  }
  return value + value2;
}

Si quieres saber de qué tipo es el parámetro, y tus tipos son objetos, una cosa que puedes hacer es meter un campo en cada uno de ellos para diferenciarlo, por ejemplo un campo type que sea string.

type RegisteredUser = { 
  username: string,
  type: "registered"
};
type Visitor = { 
  visitDate: Date,
  type: "visitor"
};

type User = RegisteredUser | Visitor;

function greetUser(user: User): void {
  if (user.type === "registered") {
    console.log("Welcome back, user");
  }
    if (user.type === "visitor") {
    console.log("Hello new visitor");
  }
}

Intersección de tipos (AND)

La intersección de tipos se hace con el operador & y lo que hace es lo contrario al que hemos visto antes, se encarga de establecer un tipo que tiene que ser a la vez de varios tipos, ejemplo:

type Visitor = { visitDate: Date };
type ExternalVisitor = { source: string };

type User = Visitor & ExternalVisitor;

En este caso el tipo User tiene que ser a la vez de tipo Visitor y de tipo ExternalVisitor, es decir, tiene que tener tanto la propiedad visitDate como la propiedad source. Si este mismo ejemplo lo hacemos con el operador OR, sería al contrario, o tiene la propiedad visitDate o tiene la propiedad source, una de las dos pero no las dos a la vez.

Es decir, con los tipos anteriores, esta variable sería correcta:

const user: User = { visitDate: new Date(), source: "codingpotions.com" };