TechLead
Lección 5 de 8
5 min de lectura
TypeScript

Tipos unión e intersección

Combina tipos con uniones e intersecciones y usa type guards para hacer narrowing

Tipos unión

Los tipos unión permiten que un valor sea de varios tipos posibles:

// Tipo unión básico
let id: string | number;
id = "abc123";  // OK
id = 123;       // OK
// id = true;   // Error!

// Unión en parámetros de función
function printId(id: string | number): void {
  console.log("ID:", id);
}

// Unión con arrays
let mixedArray: (string | number)[] = [1, "two", 3, "four"];

// Unión con tipos literales
type Direction = "north" | "south" | "east" | "west";
let heading: Direction = "north";

// Unión con null/undefined
type MaybeString = string | null | undefined;

function processName(name: string | null): string {
  if (name === null) {
    return "Anonymous";
  }
  return name.toUpperCase();
}

Narrowing de tipos

Reduce los tipos unión con type guards:

// Narrowing con typeof
function padLeft(value: string, padding: string | number): string {
  if (typeof padding === "number") {
    // padding es number aquí
    return " ".repeat(padding) + value;
  }
  // padding es string aquí
  return padding + value;
}

// Narrowing por truthiness
function processValue(value: string | null | undefined): string {
  if (value) {
    // value es string aquí (truthy)
    return value.toUpperCase();
  }
  return "default";
}

// Narrowing por igualdad
function compare(a: string | number, b: string | boolean): void {
  if (a === b) {
    // a y b son ambos string aquí
    console.log(a.toUpperCase());
  }
}

// Narrowing con operador in
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird): void {
  if ("swim" in animal) {
    animal.swim();  // animal es Fish
  } else {
    animal.fly();   // animal es Bird
  }
}

// Narrowing con instanceof
function logDate(date: Date | string): void {
  if (date instanceof Date) {
    console.log(date.toISOString());  // date es Date
  } else {
    console.log(date);  // date es string
  }
}

Uniones discriminadas

Usa una propiedad común para discriminar entre los miembros de la unión:

// Unión discriminada con propiedad 'kind'
interface Circle {
  kind: "circle";
  radius: number;
}

interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}

interface Triangle {
  kind: "triangle";
  base: number;
  height: number;
}

type Shape = Circle | Rectangle | Triangle;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rectangle":
      return shape.width * shape.height;
    case "triangle":
      return (shape.base * shape.height) / 2;
  }
}

// Verificación de exhaustividad
function assertNever(x: never): never {
  throw new Error("Unexpected value: " + x);
}

function getAreaExhaustive(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rectangle":
      return shape.width * shape.height;
    case "triangle":
      return (shape.base * shape.height) / 2;
    default:
      return assertNever(shape);  // Error si falta un caso
  }
}

Tipos de intersección

Los tipos de intersección combinan múltiples tipos en uno:

// Intersección básica
type Person = {
  name: string;
  age: number;
};

type Employee = {
  employeeId: number;
  department: string;
};

type Staff = Person & Employee;

const staff: Staff = {
  name: "Alice",
  age: 30,
  employeeId: 12345,
  department: "Engineering"
};

// Intersección con interfaces
interface Timestamps {
  createdAt: Date;
  updatedAt: Date;
}

interface Identifiable {
  id: string;
}

type Entity<T> = T & Timestamps & Identifiable;

type UserEntity = Entity<{ name: string; email: string }>;

const user: UserEntity = {
  id: "user-1",
  name: "Bob",
  email: "bob@example.com",
  createdAt: new Date(),
  updatedAt: new Date()
};

Type guards definidos por el usuario

// Función con predicado de tipo
interface Cat {
  meow(): void;
  purr(): void;
}

interface Dog {
  bark(): void;
  wagTail(): void;
}

// Type guard con palabra clave 'is'
function isCat(animal: Cat | Dog): animal is Cat {
  return "meow" in animal;
}

function interact(animal: Cat | Dog): void {
  if (isCat(animal)) {
    animal.meow();   // animal es Cat
    animal.purr();
  } else {
    animal.bark();   // animal es Dog
    animal.wagTail();
  }
}

// Type guard para respuesta de API
interface SuccessResponse {
  success: true;
  data: unknown;
}

interface ErrorResponse {
  success: false;
  error: string;
}

type ApiResponse = SuccessResponse | ErrorResponse;

function isSuccess(response: ApiResponse): response is SuccessResponse {
  return response.success === true;
}

function handleResponse(response: ApiResponse): void {
  if (isSuccess(response)) {
    console.log("Data:", response.data);
  } else {
    console.error("Error:", response.error);
  }
}

Ejemplos prácticos

// Tipos de acciones en Redux
type LoadUsersAction = {
  type: "LOAD_USERS";
};

type AddUserAction = {
  type: "ADD_USER";
  payload: { name: string; email: string };
};

type RemoveUserAction = {
  type: "REMOVE_USER";
  payload: { id: number };
};

type UserAction = LoadUsersAction | AddUserAction | RemoveUserAction;

function userReducer(state: User[], action: UserAction): User[] {
  switch (action.type) {
    case "LOAD_USERS":
      return state;
    case "ADD_USER":
      return [...state, { id: Date.now(), ...action.payload }];
    case "REMOVE_USER":
      return state.filter(u => u.id !== action.payload.id);
  }
}

// Props de componente React con variantes
type ButtonProps = {
  label: string;
  onClick: () => void;
} & (
  | { variant: "primary"; icon?: never }
  | { variant: "icon"; icon: string }
);

// Uso:
// 

Puntos clave

  • • Los tipos unión (|) permiten uno de varios tipos
  • • Los tipos de intersección (&) combinan tipos
  • • Usa type guards para reducir uniones
  • • Las uniones discriminadas usan una propiedad común
  • • Los type guards personalizados usan la palabra clave is
  • • La verificación de exhaustividad detecta casos faltantes

Continuar Aprendiendo