TypeScript: Tipos avanzados y Type Guards
Explora técnicas avanzadas de TypeScript incluyendo type guards, discriminated unions y mapped types
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.
CodeCraft Master
Desarrollador Full Stack apasionado por compartir conocimiento. Escribo sobre JavaScript, TypeScript, React y desarrollo web moderno.
Artículos Relacionados
TypescriptTypeScript Generics: Guía completa para principiantes
Domina los genéricos en TypeScript y escribe código más reutilizable y type-safe
TypescriptTypeScript Utility Types que debes conocer
Explora los utility types más útiles de TypeScript y cómo pueden simplificar tu código
Ú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