Archivos de Declaración
Receta
Crea y gestiona archivos de declaración .d.ts para tipos globales, ampliación de módulos, paquetes de terceros sin tipos y declaraciones ambientales personalizadas en un proyecto React/Next.js.
Ejemplo Funcional
// types/global.d.ts - Declaraciones de tipo globales
type User = {
id: string;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
};
type ApiResponse<T> = {
data: T;
meta: {
page: number;
totalPages: number;
};
};// types/environment.d.ts - Variables de entorno tipificadas
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development" | "production" | "test";
DATABASE_URL: string;
NEXT_PUBLIC_API_URL: string;
NEXT_PUBLIC_SITE_URL: string;
AUTH_SECRET: string;
}
}// types/modules.d.ts - Declaración de módulos sin tipos
declare module "some-untyped-library" {
export function doSomething(input: string): Promise<string>;
export function configure(options: { verbose: boolean }): void;
}
// Importaciones de assets
declare module "*.svg" {
const content: React.FC<React.SVGProps<SVGSVGElement>>;
export default content;
}
declare module "*.png" {
const src: string;
export default src;
}
declare module "*.css" {
const classes: Record<string, string>;
export default classes;
}Análisis Profundo
Cómo Funciona
- Los archivos
.d.tsson archivos de declaración que contienen solo información de tipos, sin código en runtime. Comunican a TypeScript sobre tipos que existen en runtime pero no están expresados en el código fuente de TypeScript. - Los archivos en el directorio
types/se incluyen automáticamente si tutsconfig.jsontiene"include": ["**/*.ts", "**/*.tsx"]o si la ruta coincide con un patróninclude. - Las declaraciones ambientales (
declare) dicen a TypeScript "esto existe en runtime, confía en mí". Úsalas para variables globales, módulos sin tipos y extensiones de entorno. - La ampliación de módulos extiende tipos de módulos existentes usando
declare module "module-name". Así es como añades campos aProcessEnv, extienden tipos de Next.js o parchas tipos de librerías de terceros. - TypeScript resuelve tipos en este orden: archivos fuente locales, mapeos
paths,node_modules/@types/*, luego archivos.d.tspersonalizados en directorios que coinciden coninclude.
Variaciones
Ampliación de tipos de Next.js:
// types/next-auth.d.ts - Extensión de tipos de sesión de next-auth
import { DefaultSession } from "next-auth";
declare module "next-auth" {
interface Session {
user: {
id: string;
role: "admin" | "editor" | "viewer";
} & DefaultSession["user"];
}
}Extensión de Window:
// types/window.d.ts
declare global {
interface Window {
analytics: {
track: (event: string, properties?: Record<string, unknown>) => void;
identify: (userId: string) => void;
};
__ENV__: Record<string, string>;
}
}
export {}; // Requerido para hacer esto un móduloMódulos CSS Tipificados:
// types/css-modules.d.ts
declare module "*.module.css" {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module "*.module.scss" {
const classes: { readonly [key: string]: string };
export default classes;
}Creación de un tipo utilitario global:
// types/utils.d.ts
type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
type StrictOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type Nullable<T> = T | null;Notas de TypeScript
- Los archivos de declaración con
declare global { }modifican el scope global. El archivo debe tener al menos unaimportoexportpara ser tratado como un módulo; añadeexport {}si es necesario. declare module "x"amplía un módulo. Si el módulo ya tiene tipos, tus declaraciones se fusionan. Si no, tu declaración reemplaza elanyimplícito.- No pongas código de implementación en archivos
.d.ts. Se borran durante la compilación y sirven solo como pistas de tipo. - Usa
typeRootsentsconfig.jsonpara controlar dónde busca TypeScript los archivos de declaración:"typeRoots": ["./types", "./node_modules/@types"].
Trampas
- Olvidar
export {}en un archivo condeclare globalhace que el archivo sea un script (no un módulo), y sus declaraciones pueden no fusionarse correctamente. - Las declaraciones de módulos ambientales (
declare module "x") reemplazan completamente los tipos del módulo a menos que importes del módulo primero. Para ampliar, añade una declaración de importación. - Los paquetes
@types/*de DefinitelyTyped pueden entrar en conflicto con tipos incluidos en versiones de paquetes más nuevas. Verifica si la librería incluye sus propios tipos antes de instalar@types/. skipLibCheck: trueomite la comprobación de tipos de todos los archivos.d.ts, incluyendo los tuyos. Los errores en tus declaraciones personalizadas se ignorarán silenciosamente.- Los tipos de variables de entorno (
ProcessEnv) proporcionan confianza en tiempo de compilación pero sin garantía en runtime. La variable podría seguir faltando en runtime. Siempre valida al inicio.
Alternativas
| Enfoque | Ventajas | Desventajas |
|---|---|---|
Archivos .d.ts personalizados | Control total, específico del proyecto | Debe mantenerse manualmente |
Paquetes @types/* | Mantenido por la comunidad, bien probado | Puede quedar rezagado con actualizaciones de librería |
declare inline en archivos fuente | Co-localizado con uso | Contamina archivos fuente |
Zod + z.infer | Validación en runtime + inferencia de tipos | No apto para tipos ambientales/globales |
Configuración typeRoots | Orden de resolución de tipos explícito | Fácil de malconfigurar |
| Ampliación de módulos | Extiende tipos existentes limpiamente | Requiere comprensión del sistema de módulos |
Preguntas Frecuentes
¿Cuál es el propósito de un archivo .d.ts?
- Contiene solo información de tipos, sin código en runtime.
- Comunica a TypeScript sobre tipos que existen en runtime pero no están expresados en el código fuente de TypeScript.
- Se usa comúnmente para tipos globales, variables de entorno y paquetes de terceros sin tipos.
¿Por qué necesitas export {} en un archivo que usa declare global?
- Sin él, TypeScript trata el archivo como un script, no como un módulo.
- Las declaraciones con scope de script pueden no fusionarse correctamente con tipos con scope de módulo.
- Añadir
export {}(incluso sin exportaciones reales) fuerza al archivo a ser tratado como un módulo.
¿Cómo tipificas variables de entorno en un proyecto Next.js?
// types/environment.d.ts
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development" | "production" | "test";
DATABASE_URL: string;
NEXT_PUBLIC_API_URL: string;
}
}- Esto proporciona confianza en tiempo de compilación pero sin garantía en runtime.
- Siempre valida las variables de entorno al inicio de la aplicación.
¿Cuál es la diferencia entre declaraciones ambientales y ampliación de módulos?
- Las declaraciones ambientales (
declare) comunican a TypeScript que algo existe en runtime sin proporcionar implementación. - La ampliación de módulos (
declare module "x") extiende o reemplaza tipos de un módulo existente. - Para ampliar (no reemplazar), debes importar del módulo primero antes de declarar.
¿Cómo declaras tipos para una librería de terceros sin tipos?
// types/modules.d.ts
declare module "some-untyped-library" {
export function doSomething(input: string): Promise<string>;
export function configure(options: { verbose: boolean }): void;
}- Coloca la declaración en un archivo
.d.tsincluido por tutsconfig.json. - El nombre del módulo debe coincidir exactamente con la ruta de importación.
Trampa: ¿Qué sucede si usas declare module "x" sin importar desde ese módulo primero?
- Tus declaraciones reemplazan completamente los tipos del módulo en lugar de ampliarlos.
- Se pierden los tipos existentes del paquete.
- Añade una declaración de
importdesde el módulo antes del bloquedeclare modulepara fusionar en lugar de reemplazar.
¿Cómo tipificas importaciones de SVG e imágenes en un proyecto React?
declare module "*.svg" {
const content: React.FC<React.SVGProps<SVGSVGElement>>;
export default content;
}
declare module "*.png" {
const src: string;
export default src;
}- Estas declaraciones comunican a TypeScript cómo se ve la exportación por defecto de cada tipo de asset.
¿Qué hace typeRoots en tsconfig.json?
- Controla dónde busca TypeScript los archivos de declaración.
- Ejemplo:
"typeRoots": ["./types", "./node_modules/@types"]. - Fácil de malconfigurar — si lo estableces, debes incluir explícitamente
node_modules/@typeso perderás acceso a paquetes de DefinitelyTyped.
Trampa: ¿Puede skipLibCheck: true ocultar errores en tus propios archivos .d.ts?
- Sí.
skipLibCheckomite la comprobación de tipos de todos los archivos.d.ts, incluyendo tus declaraciones personalizadas. - Los errores en tu directorio
types/se ignorarán silenciosamente. - Aún se recomienda por velocidad de compilación, pero ten en cuenta este intercambio.
¿Cuándo debes instalar paquetes @types/* vs escribir tus propias declaraciones?
- Usa
@types/*cuando la librería no incluya sus propios tipos y exista un paquete comunitario. - Escribe los tuyos cuando no existe
@types/*, o cuando necesitas anulaciones específicas del proyecto. - Verifica si la librería ya incluye tipos antes de instalar
@types/— los duplicados pueden causar conflictos.
¿Cómo extiendes el objeto Window con propiedades personalizadas en TypeScript?
// types/window.d.ts
declare global {
interface Window {
analytics: {
track: (event: string) => void;
};
}
}
export {};- Usa
declare globalpara modificar el scope global. - El archivo debe ser un módulo (incluye
export {}).