TypeScript en Producción: Guía Práctica
TypeScript se ha convertido en el estándar de facto para el desarrollo JavaScript en equipos grandes. Pero usar TypeScript no garantiza código robusto. Aquí compartimos nuestras lecciones aprendidas en Binary Core.
Arquitectura de Tipos
Tipos de Dominio vs Tipos de Implementación
typescript// ❌ Mal: Tipos de implementación expuestos interface UserRepository { save(user: { id: string; name: string; email: string }): Promise<void>; } // ✅ Bien: Tipos de dominio separados interface User { id: UserId; name: string; email: Email; } class UserId extends Brand<string, 'UserId'> {} class Email extends Brand<string, 'Email'> {} interface UserRepository { save(user: User): Promise<void>; }
Tipos Discriminados
Los tipos discriminados son esenciales para modelar estados complejos:
typescripttype AsyncState<T> = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error }; function handleState<T>(state: AsyncState<T>) { switch (state.status) { case 'idle': return 'Nothing happening'; case 'loading': return 'Loading...'; case 'success': return `Got ${state.data}`; case 'error': return `Error: ${state.error.message}`; } }
Patrones Avanzados
Template Literal Types
Para validación de strings en tiempo de compilación:
typescripttype EventName = `on${Capitalize<string>}`; type ValidEvents = EventName extends `on${infer E}` ? E extends 'Click' | 'Hover' | 'Focus' ? E : never : never; // ValidEvents = 'Click' | 'Hover' | 'Focus'
Inferencia de Tipos
Deja que TypeScript infiera cuando sea posible:
typescript// ❌ Mal: Especificar tipos innecesariamente const users: User[] = await fetchUsers(); // ✅ Bien: Inferir del contexto const users = await fetchUsers<User>(); // ✅ Mejor: Tipos de retorno explícitos para funciones públicas async function fetchUsers(): Promise<User[]> { const response = await fetch('/api/users'); return response.json(); }
Configuración de TypeScript
Strict Mode
Nunca desactives el strict mode. Si algo no compila, arregla el código, no el compilador:
json{ "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true } }
Path Aliases
Usa path aliases para imports limpios:
json{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@components/*": ["src/components/*"], "@lib/*": ["src/lib/*"] } } }
Testing con TypeScript
Tipos de Test
Los tests también deben ser type-safe:
typescriptdescribe('UserRepository', () => { it('should save user', async () => { const user: User = { id: new UserId('123'), name: 'John', email: new Email('john@example.com') }; await repository.save(user); const saved = await repository.findById(user.id); expect(saved).toEqual(user); }); });
Performance
Evita any
any es el enemigo de TypeScript. Usa unknown cuando no conoces el tipo:
typescript// ❌ Mal function processData(data: any) { return data.value; } // ✅ Bien function processData(data: unknown) { if (typeof data === 'object' && data !== null && 'value' in data) { return (data as { value: unknown }).value; } throw new Error('Invalid data'); }
Type Guards
Crea type guards reutilizables:
typescriptfunction isUser(value: unknown): value is User { return ( typeof value === 'object' && value !== null && 'id' in value && 'name' in value && 'email' in value ); } function processValue(value: unknown) { if (isUser(value)) { // TypeScript sabe que value es User console.log(value.name); } }
Herramientas
ESLint + TypeScript
json{ "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking" ], "rules": { "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/explicit-function-return-type": "warn", "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] } }
Conclusión
TypeScript es una herramienta poderosa, pero requiere disciplina. En Binary Core, cada línea de código pasa por revisión de tipos antes de llegar a producción.
Binary Core
Equipo Binary Core