TypeScript: Tipos avanzados y Type Guards

Explora técnicas avanzadas de TypeScript incluyendo type guards, discriminated unions y mapped types

CodeCraft Master
17 de febrero de 2026
11 min
6346 views

TypeScript: Tipos avanzados y Type Guards

Dominar los tipos avanzados de TypeScript te permite crear código más robusto y expresivo. Exploremos técnicas que llevarán tu TypeScript al siguiente nivel.

Type Guards básicos

// typeof guard
function printValue(value: string | number) {
  if (typeof value === 'string') {
    console.log(value.toUpperCase());
  } else {
    console.log(value.toFixed(2));
  }
}

// instanceof guard
class Dog {
  bark() { console.log('Woof!'); }
}

class Cat {
  meow() { console.log('Meow!'); }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

Custom Type Guards

interface Fish {
  swim(): void;
}

interface Bird {
  fly(): void;
}

// Type predicate
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim();
  } else {
    pet.fly();
  }
}

Discriminated Unions

interface Square {
  kind: 'square';
  size: number;
}

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

interface Circle {
  kind: 'circle';
  radius: number;
}

type Shape = Square | Rectangle | Circle;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'square':
      return shape.size ** 2;
    case 'rectangle':
      return shape.width * shape.height;
    case 'circle':
      return Math.PI * shape.radius ** 2;
  }
}

Mapped Types

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Partial<T> = {
  [P in keyof T]?: T[P];
};

interface User {
  id: number;
  name: string;
  email: string;
}

type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;

// Custom mapped type
type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};

type NullableUser = Nullable<User>;
// { id: number | null; name: string | null; email: string | null; }

Conditional Types

type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false

// Más complejo
type TypeName<T> =
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object";

type T0 = TypeName<string>;  // "string"
type T1 = TypeName<"a">;     // "string"
type T2 = TypeName<true>;    // "boolean"

Infer keyword

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

function getUser() {
  return { id: 1, name: 'Roberto' };
}

type User = ReturnType<typeof getUser>;
// { id: number; name: string; }

// Extraer tipos de arrays
type Unpacked<T> = 
  T extends (infer U)[] ? U :
  T extends Promise<infer U> ? U :
  T;

type T0 = Unpacked<string[]>;        // string
type T1 = Unpacked<Promise<number>>; // number

Template Literal Types

type EventName = 'click' | 'scroll' | 'mousemove';
type EventHandler = `on${Capitalize<EventName>}`;
// 'onClick' | 'onScroll' | 'onMousemove'

type AllowedColors = 'red' | 'green' | 'blue';
type ColorShades = `${AllowedColors}-${'100' | '200' | '300'}`;
// 'red-100' | 'red-200' | 'red-300' | 'green-100' | ...

Index Access Types

interface Person {
  name: string;
  age: number;
  location: {
    city: string;
    country: string;
  };
}

type Age = Person['age'];                    // number
type LocationCity = Person['location']['city']; // string

// Acceso con union
type PersonProps = Person['name' | 'age'];  // string | number

// Todas las propiedades
type AllPersonValues = Person[keyof Person];
// string | number | { city: string; country: string; }

Assertion Functions

function assert(condition: any, message: string): asserts condition {
  if (!condition) {
    throw new Error(message);
  }
}

function processValue(value: string | null) {
  assert(value !== null, 'Value must not be null');
  // Aquí TypeScript sabe que value es string
  console.log(value.toUpperCase());
}

// Assertion con type predicate
function assertIsString(value: any): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('Value must be a string');
  }
}

Recursive Types

type JSONValue =
  | string
  | number
  | boolean
  | null
  | JSONValue[]
  | { [key: string]: JSONValue };

const data: JSONValue = {
  name: 'Roberto',
  age: 25,
  hobbies: ['coding', 'gaming'],
  metadata: {
    created: '2024-01-01',
    tags: ['developer', 'content-creator']
  }
};

Branded Types

type UserId = number & { readonly brand: unique symbol };
type ProductId = number & { readonly brand: unique symbol };

function createUserId(id: number): UserId {
  return id as UserId;
}

function createProductId(id: number): ProductId {
  return id as ProductId;
}

function getUser(id: UserId) {
  // Solo acepta UserId, no ProductId ni number
}

const userId = createUserId(123);
const productId = createProductId(456);

getUser(userId);      // ✓ OK
// getUser(productId); // ✗ Error
// getUser(123);       // ✗ Error

Conclusión

Estos tipos avanzados te permiten crear APIs type-safe extremadamente expresivas. Aunque pueden parecer complejos, son herramientas poderosas que mejoran significativamente la calidad de tu código TypeScript.

C

CodeCraft Master

Desarrollador Full Stack apasionado por compartir conocimiento. Escribo sobre JavaScript, TypeScript, React y desarrollo web moderno.

Artículos Relacionados

Únete a nuestro boletín

Recibe contenido exclusivo de desarrollo web directamente en tu bandeja de entrada.

  • Contenido exclusivo de desarrollo web

  • Actualizaciones semanales

  • Tips y trucos

  • Sin spam, solo contenido de calidad

Al suscribirte, aceptas recibir correos electrónicos. Puedes cancelar tu suscripción en cualquier momento.