7 Técnicas Avanzadas de TypeScript que Deberías Conocer en 2025
Hola HaWkers, TypeScript se consolidó como la elección estándar para proyectos JavaScript serios. En 2025, más del 78% de los nuevos proyectos web utilizan TypeScript desde el inicio, según investigaciones recientes de la comunidad.
¿Dominas lo básico de TypeScript pero sientes que estás solo arañando la superficie? Este artículo va más allá de interfaces y tipos básicos, explorando técnicas que separan desarrolladores intermedios de especialistas en TypeScript.
1. Strict Mode: La Base de Código Seguro
El modo strict de TypeScript no es una técnica única, pero un conjunto de flags que hacen tu código drásticamente más seguro. En 2025, proyectos sin strict: true son considerados legacy.
// tsconfig.json - Configuración moderna
{
"compilerOptions": {
"strict": true, // Activa todas las flags strict
"noUncheckedIndexedAccess": true, // TS 5.0+
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true
}
}Lo que muchos no perciben es que strict: true activa varias flags individualmente:
noImplicitAny: ProhíbeanyimplícitostrictNullChecks:nullyundefinedson tipos distintosstrictFunctionTypes: Verificación contravariant de parámetrosstrictBindCallApply: Tipado correcto para.bind(),.call(),.apply()strictPropertyInitialization: Propiedades de clase deben ser inicializadasnoImplicitThis:thisdebe tener tipo explícito en contextos ambiguos
Pero el verdadero poder viene de las flags adicionales como noUncheckedIndexedAccess, introducida en versiones más recientes.
// Sin noUncheckedIndexedAccess
const users: Record<string, User> = {};
const user = users['123']; // Type: User
user.name; // ❌ Runtime error: Cannot read property 'name' of undefined
// Con noUncheckedIndexedAccess
const users: Record<string, User> = {};
const user = users['123']; // Type: User | undefined
// user.name; // ❌ Error de compilación
user?.name; // ✅ SeguroEsta flag sola previene incontables bugs en producción relacionados a accesos a objetos que pueden no existir.
2. Template Literal Types: Tipos Dinámicos Poderosos
Template Literal Types, introducidos en TypeScript 4.1 y expandidos en versiones posteriores, permiten crear tipos basados en strings de forma dinámica.
// Creando tipos dinámicos para eventos
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`;
// Type: 'onClick' | 'onFocus' | 'onBlur'
type EventHandlers = {
[K in HandlerName]: (event: Event) => void;
};
// Uso práctico
const handlers: EventHandlers = {
onClick: (e) => console.log('Clicked'),
onFocus: (e) => console.log('Focused'),
onBlur: (e) => console.log('Blurred')
};Esto es extremadamente poderoso para APIs que siguen convenciones de nomenclatura. Mira un ejemplo real con rutas de API:
// Sistema de rutas type-safe
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Resource = 'user' | 'product' | 'order';
type Route = `/${Resource}` | `/${Resource}/${string}`;
type RouteHandler<M extends HTTPMethod, R extends Route> = {
method: M;
route: R;
handler: (req: Request) => Promise<Response>;
};
// Uso con autocomplete total
const userRoute: RouteHandler<'GET', '/user'> = {
method: 'GET',
route: '/user',
handler: async (req) => {
// Implementation
return new Response();
}
};
// ❌ Error de compilación: 'PATCH' no existe en HTTPMethod
// const invalidRoute: RouteHandler<'PATCH', '/user'> = { ... };
3. Conditional Types e Inferencia: Lógica en Tipos
Conditional Types permiten crear lógica compleja en el sistema de tipos, similar a operadores ternarios en JavaScript.
// Type que extrae el tipo de retorno de una Promise
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number
// Aplicación práctica: función que retorna Promise o valor directo
async function fetchData<T>(
useCache: boolean,
data: T
): Promise<UnwrapPromise<T>> {
if (useCache) {
return data as UnwrapPromise<T>;
}
// Simula fetch asíncrono
return new Promise(resolve =>
setTimeout(() => resolve(data as UnwrapPromise<T>), 100)
) as any;
}Un patrón avanzado es combinar conditional types con mapped types:
// Transforma propiedades opcionales en obligatorias y viceversa
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
type RequiredKeys<T, K extends keyof T> = T & {
[P in K]-?: T[P];
};
interface User {
id: string;
name?: string;
email?: string;
}
// Hace name y email obligatorios
type FullUser = RequiredKeys<User, 'name' | 'email'>;
// Type: { id: string; name: string; email: string; }Este patrón es increíblemente útil para validación de formularios y DTOs donde ciertas propiedades se vuelven obligatorias en contextos específicos.
4. Branded Types: Seguridad en Tipos Primitivos
Branded Types (o Nominal Types) resuelven un problema común: ¿cómo diferenciar strings con significados diferentes?
// Problema: userId y productId son ambos strings
function getUser(id: string) { /* ... */ }
function getProduct(id: string) { /* ... */ }
const userId = "user123";
const productId = "prod456";
getUser(productId); // ❌ Bug, pero TypeScript no reclamaSolución con Branded Types:
// Creando tipos branded
type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, 'UserId'>;
type ProductId = Brand<string, 'ProductId'>;
// Funciones de creación (smart constructors)
function createUserId(id: string): UserId {
// Validación si es necesario
return id as UserId;
}
function createProductId(id: string): ProductId {
return id as ProductId;
}
// Uso type-safe
function getUser(id: UserId) { /* ... */ }
function getProduct(id: ProductId) { /* ... */ }
const userId = createUserId("user123");
const productId = createProductId("prod456");
getUser(userId); // ✅ OK
// getUser(productId); // ❌ ¡Error de compilación!Esto es especialmente valioso en sistemas financieros donde trabajas con diferentes monedas o IDs de diferentes dominios.
5. Utility Types Avanzados: Más Allá del Partial y Pick
TypeScript ofrece utility types poderosos, pero la mayoría de los desarrolladores usa solo Partial, Pick y Omit. Vamos más allá:
// ReturnType: Extrae tipo de retorno de función
function createUser() {
return {
id: '123',
name: 'John',
createdAt: new Date()
};
}
type User = ReturnType<typeof createUser>;
// Type: { id: string; name: string; createdAt: Date; }
// Parameters: Extrae tipos de parámetros de función
function updateUser(id: string, data: { name: string; email: string }) {
// Implementation
}
type UpdateUserParams = Parameters<typeof updateUser>;
// Type: [id: string, data: { name: string; email: string; }]
// Awaited: Unwrap Promise types (TS 4.5+)
type Response = Awaited<Promise<{ data: string }>>;
// Type: { data: string; }
// Combinando utility types
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type UserKeys = keyof User; // 'id' | 'name' | 'createdAt'Un patrón poderoso es crear tus propios utility types customizados:
// DeepPartial: Hace todas las propiedades anidadas opcionales
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
interface Config {
database: {
host: string;
port: number;
credentials: {
user: string;
password: string;
};
};
cache: {
ttl: number;
};
}
// Permite updates parciales profundos
function updateConfig(config: DeepPartial<Config>) {
// Puedes pasar solo lo que quieres actualizar
}
updateConfig({
database: {
credentials: {
password: 'new-password' // Solo password, resto es opcional
}
}
});
6. Discriminated Unions: Type Guards Automáticos
Discriminated Unions (o Tagged Unions) son un patrón que hace impossible states imposibles de representar.
// Estado de requisición usando union discriminada
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function handleRequest<T>(state: RequestState<T>) {
// TypeScript estrecha el tipo automáticamente
switch (state.status) {
case 'idle':
// state.status es 'idle'
// state.data no existe (error de compilación si intentas acceder)
return 'Esperando...';
case 'loading':
return 'Cargando...';
case 'success':
// ¡TypeScript sabe que state.data existe aquí!
return `Éxito: ${state.data}`;
case 'error':
// ¡TypeScript sabe que state.error existe aquí!
return `Error: ${state.error.message}`;
}
}
// Uso en componentes React
function UserProfile() {
const [userState, setUserState] =
useState<RequestState<User>>({ status: 'idle' });
useEffect(() => {
setUserState({ status: 'loading' });
fetchUser()
.then(data => setUserState({ status: 'success', data }))
.catch(error => setUserState({ status: 'error', error }));
}, []);
return <div>{handleRequest(userState)}</div>;
}Este patrón elimina bugs comunes donde accedes data cuando estás en estado de loading, o intentas mostrar error cuando en verdad tienes éxito.
7. Type Predicates: Type Guards Customizados
Type predicates permiten crear funciones que informan al TypeScript sobre el tipo de un valor en runtime.
// Type predicate básico
function isString(value: unknown): value is string {
return typeof value === 'string';
}
// Uso
function processValue(value: string | number) {
if (isString(value)) {
// TypeScript sabe que value es string aquí
console.log(value.toUpperCase());
} else {
// TypeScript sabe que value es number aquí
console.log(value.toFixed(2));
}
}
// Type predicate avanzado con validación
interface User {
id: string;
name: string;
email: string;
}
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'name' in obj &&
'email' in obj &&
typeof (obj as any).id === 'string' &&
typeof (obj as any).name === 'string' &&
typeof (obj as any).email === 'string'
);
}
// Uso con API responses
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
if (!isUser(data)) {
throw new Error('Invalid user data from API');
}
// TypeScript sabe que data es User aquí
return data;
}
Bonus: Integración con Zod para Runtime Validation
TypeScript verifica tipos en compile-time, pero APIs retornan datos en runtime. Zod cierra esa brecha:
import { z } from 'zod';
// Schema Zod
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().int().positive().optional()
});
// TypeScript type inferido automáticamente
type User = z.infer<typeof UserSchema>;
// Validación en runtime con type narrowing
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
// Parse valida y retorna User o lanza error
return UserSchema.parse(data);
}
// Validación segura sin excepciones
const result = UserSchema.safeParse(unknownData);
if (result.success) {
// result.data es User
console.log(result.data.name);
} else {
// result.error contiene detalles
console.error(result.error.errors);
}Esta combinación de TypeScript con Zod es el patrón de oro en 2025 para aplicaciones que manejan datos externos.
El Impacto Real de Estas Técnicas
Implementar estas técnicas no es sobre escribir código "más bonito" — es sobre prevenir bugs. En un estudio de 2024, equipos que adoptaron strict mode + branded types + discriminated unions reportaron:
- 38% menos bugs en producción relacionados a tipos incorrectos
- 22% de reducción en tiempo de debugging
- Aumento del 15% en la confianza al refactorizar código
TypeScript bien usado transforma tu editor en un asistente que previene errores antes de que ejecutes el código.
¿Quieres entender mejor los fundamentos de JavaScript que hacen TypeScript posible? Confiere mi artículo sobre Programación Funcional en JavaScript donde vas a descubrir conceptos que TypeScript utiliza para inferencia de tipos.
¡Vamos a por ello! 🦅
¿Quieres Profundizar Tus Conocimientos en JavaScript?
Este artículo cubrió técnicas avanzadas de TypeScript, pero hay mucho más para explorar en el mundo del desarrollo moderno.
Desarrolladores que invierten en conocimiento sólido y estructurado tienden a tener más oportunidades en el mercado.
Material de Estudio Completo
Si quieres dominar JavaScript del básico al avanzado, preparé una guía completa:
Opciones de inversión:
- $9.90 USD (pago único)
💡 Material actualizado con las mejores prácticas del mercado

