TypeScript 5.7: Nuevos Recursos y Mejores Prácticas en 2025
Hola HaWkers, TypeScript continúa evolucionando como la forma preferida de escribir JavaScript tipado. En 2025, con la versión 5.7, tenemos recursos aún más poderosos para escribir código seguro y mantenible.
¿Ya exploraste las novedades más recientes? Vamos a ver lo que cambió y las mejores prácticas para aprovechar todo el potencial del TypeScript.
Novedades del TypeScript 5.7
1. Path Rewriting para Imports Relativos
TypeScript 5.7 ahora soporta rewriting de paths en imports relativos, facilitando configuraciones de build.
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "bundler",
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@utils/*": ["./src/utils/*"]
},
"rewriteRelativeImportExtensions": true
}
}
// Ahora puedes usar
import { Button } from '@components/button';
import { formatDate } from '@utils/date';2. Inferencia Mejorada en Control Flow
// TypeScript 5.7 infiere mejor en switch/case
type Animal = { type: 'dog'; bark: () => void } | { type: 'cat'; meow: () => void };
function handleAnimal(animal: Animal) {
switch (animal.type) {
case 'dog':
// TypeScript sabe que animal es { type: 'dog'; bark: () => void }
animal.bark();
break;
case 'cat':
// TypeScript sabe que animal es { type: 'cat'; meow: () => void }
animal.meow();
break;
}
}
// Inferencia en condiciones complejas
function process(value: string | number | null) {
if (typeof value === 'string' && value.length > 0) {
// value es string con length > 0
return value.toUpperCase();
}
if (typeof value === 'number' && !Number.isNaN(value)) {
// value es number válido
return value.toFixed(2);
}
return 'empty';
}3. Const Type Parameters
// Antes
function routes<T extends readonly string[]>(paths: T): T {
return paths;
}
const r = routes(['/', '/about', '/contact'] as const);
// Ahora - const type parameter
function routes<const T extends readonly string[]>(paths: T): T {
return paths;
}
// Infiere tipo literal automáticamente
const r = routes(['/', '/about', '/contact']);
// Tipo: readonly ['/', '/about', '/contact']
Mejores Prácticas en 2025
1. Usar satisfies Operator
El operador satisfies verifica tipos sin perder inferencia.
// Problema con anotación de tipo
const config: Record<string, string | number> = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
};
// config.apiUrl es string | number (perdió especificidad)
// Solución con satisfies
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
} satisfies Record<string, string | number>;
// config.apiUrl es string (preservó especificidad)
// config.timeout es number// Caso de uso real: objeto de traducción
type TranslationKeys = 'greeting' | 'farewell' | 'error';
const translations = {
greeting: 'Hola',
farewell: 'Adiós',
error: 'Ocurrió un error',
} satisfies Record<TranslationKeys, string>;
// TypeScript verifica que todas las claves existen
// Y preserva el tipo literal de cada valor2. Template Literal Types Avanzados
// Rutas de API tipadas
type ApiRoute<T extends string> = `/api/${T}`;
type UserRoutes = ApiRoute<'users' | 'users/:id' | 'users/:id/posts'>;
// '/api/users' | '/api/users/:id' | '/api/users/:id/posts'
// Extrair parámetros de ruta
type ExtractParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParams<`/${Rest}`>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractParams<'/users/:userId/posts/:postId'>;
// 'userId' | 'postId'
// Convertir snake_case para camelCase
type SnakeToCamel<S extends string> =
S extends `${infer T}_${infer U}`
? `${Lowercase<T>}${Capitalize<SnakeToCamel<U>>}`
: S;
type Result = SnakeToCamel<'user_first_name'>;
// 'userFirstName'
3. Branded Types para Seguridad
// Crear tipos nominales para evitar confusión
declare const brand: unique symbol;
type Brand<T, B> = T & { [brand]: B };
// Tipos brandados
type UserId = Brand<string, 'UserId'>;
type PostId = Brand<string, 'PostId'>;
type Email = Brand<string, 'Email'>;
// Funciones que crean tipos brandados (com validación)
function createUserId(id: string): UserId {
if (!id.match(/^user_[a-z0-9]+$/)) {
throw new Error('Invalid user ID format');
}
return id as UserId;
}
function createEmail(email: string): Email {
if (!email.includes('@')) {
throw new Error('Invalid email format');
}
return email as Email;
}
// Uso
function getUser(id: UserId): User { /* ... */ }
function sendEmail(to: Email): void { /* ... */ }
const userId = createUserId('user_abc123');
const postId = 'post_xyz' as PostId;
getUser(userId); // OK
getUser(postId); // Error! PostId não é compatível com UserId4. Utility Types Customizados
// DeepPartial - todos los niveles opcionales
type DeepPartial<T> = T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T;
// DeepRequired - todos los niveles obligatorios
type DeepRequired<T> = T extends object
? { [P in keyof T]-?: DeepRequired<T[P]> }
: T;
// DeepReadonly - todos los niveles inmutables
type DeepReadonly<T> = T extends object
? { readonly [P in keyof T]: DeepReadonly<T[P]> }
: T;
// Exemplo de uso
interface Config {
database: {
host: string;
port: number;
credentials?: {
user: string;
password: string;
};
};
features: {
darkMode: boolean;
analytics: boolean;
};
}
// Configuración parcial para override
type PartialConfig = DeepPartial<Config>;
const overrides: PartialConfig = {
database: {
port: 5433, // Solo override del puerto
},
};
5. Inferencia Condicional Avanzada
// Extrair tipo de retorno de Promise
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
// Extrair tipo de elementos de array
type ArrayElement<T> = T extends (infer U)[] ? U : never;
// Extrair tipos de función
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never;
// Ejemplo práctico: Tipado de API response
type ApiResponse<T> = {
data: T;
status: number;
message: string;
};
type ExtractData<T> = T extends ApiResponse<infer D> ? D : never;
// Uso
async function fetchUser(id: string): Promise<ApiResponse<User>> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
type UserData = ExtractData<Awaited<ReturnType<typeof fetchUser>>>;
// User6. Pattern Matching con Discriminated Unions
// Estado de request con pattern matching
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
// Função exhaustiva para tratar todos los estados
function renderRequestState<T>(
state: RequestState<T>,
render: {
idle: () => React.ReactNode;
loading: () => React.ReactNode;
success: (data: T) => React.ReactNode;
error: (error: Error) => React.ReactNode;
}
): React.ReactNode {
switch (state.status) {
case 'idle':
return render.idle();
case 'loading':
return render.loading();
case 'success':
return render.success(state.data);
case 'error':
return render.error(state.error);
default:
// Verificación exhaustiva
const _exhaustive: never = state;
return _exhaustive;
}
}
// Uso
function UserProfile({ state }: { state: RequestState<User> }) {
return renderRequestState(state, {
idle: () => <p>Clic para cargar</p>,
loading: () => <Spinner />,
success: (user) => <UserCard user={user} />,
error: (err) => <ErrorMessage message={err.message} />,
});
}
7. Type Guards Personalizados
// Type guard simple
function isString(value: unknown): value is string {
return typeof value === 'string';
}
// Type guard para objetos
interface User {
id: string;
name: string;
email: string;
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
'email' in value &&
typeof (value as User).id === 'string' &&
typeof (value as User).name === 'string' &&
typeof (value as User).email === 'string'
);
}
// Type guard com assertion
function assertIsUser(value: unknown): asserts value is User {
if (!isUser(value)) {
throw new Error('Value is not a User');
}
}
// Uso
function processUser(data: unknown) {
assertIsUser(data); // Lanza si inválido
// A partir de aquí, data es User
console.log(data.name);
}8. Builder Pattern Tipado
// Builder com tipo acumulativo
type QueryBuilder<T extends object = {}> = {
select<K extends string>(
fields: K[]
): QueryBuilder<T & { select: K[] }>;
from<Table extends string>(
table: Table
): QueryBuilder<T & { from: Table }>;
where<Condition extends string>(
condition: Condition
): QueryBuilder<T & { where: Condition }>;
build(): T extends { select: infer S; from: infer F }
? { query: string; params: T }
: never;
};
function createQueryBuilder(): QueryBuilder {
const state: any = {};
return {
select(fields) {
state.select = fields;
return this as any;
},
from(table) {
state.from = table;
return this as any;
},
where(condition) {
state.where = condition;
return this as any;
},
build() {
if (!state.select || !state.from) {
throw new Error('select y from son obligatorios');
}
return {
query: `SELECT ${state.select.join(', ')} FROM ${state.from}${
state.where ? ` WHERE ${state.where}` : ''
}`,
params: state,
};
},
};
}
// Uso - TypeScript garantiza que select y from son llamados
const query = createQueryBuilder()
.select(['id', 'name', 'email'])
.from('users')
.where('active = true')
.build();
9. Configuración Recomendada tsconfig.json
{
"compilerOptions": {
// Strict mode - siempre activar
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
// Module system
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"isolatedModules": true,
// Output
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
// Paths
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
// Performance
"skipLibCheck": true,
"incremental": true,
// Compatibility
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"verbatimModuleSyntax": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}Conclusión
TypeScript en 2025 es más poderoso y expresivo que nunca. Las novedades de la versión 5.7 y las mejores prácticas presentadas permiten escribir código:
- Más seguro: Tipos brandados y guards
- Más expresivo: Template literals y conditional types
- Más mantenible: Patrones de builder y discriminated unions
El secreto es usar TypeScript a favor, no contra. Tipos deben documentar intención y prevenir errores, no burocratizar el código.
Si quieres profundizar en el ecosistema JavaScript moderno, recomiendo que veas otro artículo: ECMAScript 2025: Nuevos Recursos de JavaScript donde vas a descubrir las novedades del lenguaje que TypeScript soporta.

