TypeScript

TypeScript

Tutorial TypeScript: Namespaces

Aprende a usar namespaces en TypeScript para organizar y encapsular código, evitando conflictos y mejorando la estructura en proyectos grandes.

Aprende TypeScript y certifícate

Declaración de namespaces

Los namespaces en TypeScript proporcionan una forma de organizar y encapsular el código, evitando la contaminación del ámbito global. Funcionan como contenedores lógicos que agrupan funcionalidades relacionadas bajo un mismo nombre, permitiendo una mejor estructuración del código en aplicaciones de gran escala.

Para declarar un namespace en TypeScript, utilizamos la palabra clave namespace seguida del nombre que queremos asignarle. Todo el código relacionado se coloca dentro de llaves, creando así un ámbito aislado.

namespace Validacion {
    // Código dentro del namespace
}

Dentro de un namespace podemos definir interfaces, clases, funciones, variables y otros elementos que queramos agrupar:

namespace Validacion {
    // Interfaz dentro del namespace
    export interface StringValidator {
        esValido(s: string): boolean;
    }
    
    // Variable dentro del namespace
    const mensajeError = "Formato inválido";
    
    // Clase dentro del namespace
    export class RegexValidator implements StringValidator {
        private regex: RegExp;
        
        constructor(regex: string) {
            this.regex = new RegExp(regex);
        }
        
        esValido(s: string): boolean {
            return this.regex.test(s);
        }
    }
    
    // Función dentro del namespace
    export function validarFormato(s: string, validator: StringValidator): boolean {
        return validator.esValido(s);
    }
}

Exportación de elementos

Es importante destacar que, por defecto, los elementos declarados dentro de un namespace son privados y solo accesibles dentro del propio namespace. Para hacerlos visibles fuera del namespace, debemos utilizar la palabra clave export:

namespace Utilidades {
    // Privado - solo accesible dentro del namespace
    function formatearFecha(fecha: Date): string {
        return fecha.toISOString();
    }
    
    // Público - accesible desde fuera del namespace
    export function obtenerFechaFormateada(): string {
        const ahora = new Date();
        return formatearFecha(ahora);
    }
}

// Uso del elemento exportado
const fecha = Utilidades.obtenerFechaFormateada();
// Error: Utilidades.formatearFecha no es accesible
// const fechaDirecta = Utilidades.formatearFecha(new Date());

Uso de namespaces

Para utilizar un namespace declarado en el mismo archivo, simplemente accedemos a sus miembros exportados mediante la notación de punto:

namespace Matematicas {
    export function sumar(a: number, b: number): number {
        return a + b;
    }
    
    export function multiplicar(a: number, b: number): number {
        return a * b;
    }
}

// Uso del namespace
const resultado = Matematicas.sumar(5, 3);
console.log(resultado); // 8

Namespaces en múltiples archivos

Los namespaces pueden dividirse en múltiples archivos utilizando la sintaxis de referencia de triple barra oblicua (///). Esto permite mantener el código organizado en archivos separados pero lógicamente agrupados bajo el mismo namespace:

validadores.ts:

namespace Validacion {
    export interface StringValidator {
        esValido(s: string): boolean;
    }
}

validadorEmail.ts:

/// <reference path="validadores.ts" />
namespace Validacion {
    const patronEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    
    export class ValidadorEmail implements StringValidator {
        esValido(s: string): boolean {
            return patronEmail.test(s);
        }
    }
}

validadorCodigo.ts:

/// <reference path="validadores.ts" />
namespace Validacion {
    const patronCodigo = /^[A-Z]{2}-\d{4}$/;
    
    export class ValidadorCodigo implements StringValidator {
        esValido(s: string): boolean {
            return patronCodigo.test(s);
        }
    }
}

app.ts:

/// <reference path="validadorEmail.ts" />
/// <reference path="validadorCodigo.ts" />

// Crear instancias de los validadores
let validadores: { [s: string]: Validacion.StringValidator } = {};
validadores["email"] = new Validacion.ValidadorEmail();
validadores["codigo"] = new Validacion.ValidadorCodigo();

// Validar algunas entradas
let entradas = ["usuario@dominio.com", "ABC-1234"];
entradas.forEach(entrada => {
    for (let tipo in validadores) {
        console.log(`'${entrada}' - ${validadores[tipo].esValido(entrada) ? "válido" : "inválido"} como ${tipo}`);
    }
});

Compilación de namespaces en múltiples archivos

Para compilar namespaces distribuidos en múltiples archivos, tenemos dos opciones:

1. Compilación con outFile:

tsc --outFile app.js validadores.ts validadorEmail.ts validadorCodigo.ts app.ts

2. Compilación individual y uso de referencias:

tsc validadores.ts validadorEmail.ts validadorCodigo.ts app.ts

Alias de namespaces

Podemos crear alias para namespaces con nombres largos o para evitar conflictos de nombres:

namespace Empresa {
    export namespace Contabilidad {
        export class Factura {
            // Implementación
        }
    }
}

// Crear un alias para simplificar el acceso
import FacturaContable = Empresa.Contabilidad.Factura;

// Uso del alias
let factura = new FacturaContable();

Consideraciones de uso

Al declarar namespaces, es recomendable seguir estas buenas prácticas:

  • Utiliza nombres descriptivos que reflejen el propósito del namespace
  • Exporta solo los elementos que necesiten ser accesibles desde fuera
  • Mantén una estructura jerárquica lógica cuando uses namespaces anidados
  • Considera usar módulos ES para proyectos nuevos, ya que ofrecen mejor soporte para herramientas modernas
// Ejemplo de namespace bien estructurado
namespace Aplicacion {
    export namespace Modelos {
        export interface Usuario {
            id: number;
            nombre: string;
            email: string;
        }
    }
    
    export namespace Servicios {
        export class UsuarioServicio {
            obtenerUsuario(id: number): Modelos.Usuario {
                // Implementación
                return { id, nombre: "Usuario", email: "usuario@ejemplo.com" };
            }
        }
    }
}

// Uso del namespace
const servicio = new Aplicacion.Servicios.UsuarioServicio();
const usuario = servicio.obtenerUsuario(1);

Los namespaces son particularmente útiles en aplicaciones grandes o cuando se trabaja con código de terceros que podría tener conflictos de nombres. Sin embargo, para proyectos modernos, el sistema de módulos ES (ECMAScript) suele ser la opción recomendada por su mejor integración con herramientas de empaquetado como webpack.

Namespaces anidados

Los namespaces anidados permiten crear estructuras jerárquicas para organizar el código en TypeScript, facilitando la gestión de aplicaciones complejas mediante niveles adicionales de encapsulamiento. Esta técnica resulta especialmente útil cuando necesitamos agrupar funcionalidades relacionadas dentro de un contexto más amplio.

Para crear namespaces anidados, simplemente declaramos un namespace dentro de otro. La sintaxis es intuitiva y mantiene la misma estructura que los namespaces simples:

namespace Empresa {
    export namespace Finanzas {
        export class Presupuesto {
            constructor(public total: number) {}
            
            calcularImpuestos(): number {
                return this.total * 0.21;
            }
        }
    }
}

En este ejemplo, Finanzas es un namespace anidado dentro del namespace principal Empresa. Para acceder a la clase Presupuesto, utilizamos la notación de punto completa:

// Acceso a elementos de namespaces anidados
const presupuesto = new Empresa.Finanzas.Presupuesto(10000);
console.log(presupuesto.calcularImpuestos()); // 2100

Múltiples niveles de anidamiento

Los namespaces pueden anidarse en múltiples niveles, creando estructuras más profundas cuando sea necesario:

namespace Aplicacion {
    export namespace Datos {
        export namespace Repositorios {
            export class UsuarioRepositorio {
                obtenerTodos(): string[] {
                    return ["Ana", "Carlos", "Elena"];
                }
            }
        }
        
        export namespace Servicios {
            export class ConexionBD {
                conectar(): boolean {
                    console.log("Conectando a la base de datos...");
                    return true;
                }
            }
        }
    }
}

// Uso de namespaces con múltiples niveles
const repo = new Aplicacion.Datos.Repositorios.UsuarioRepositorio();
const usuarios = repo.obtenerTodos();
console.log(usuarios); // ["Ana", "Carlos", "Elena"]

Importaciones internas entre namespaces anidados

Los namespaces anidados pueden acceder a elementos de otros namespaces dentro de la misma jerarquía, siempre que estén exportados:

namespace Sistema {
    // Namespace para constantes y configuraciones
    export namespace Config {
        export const API_URL = "https://api.ejemplo.com";
        export const TIMEOUT = 5000;
    }
    
    // Namespace para servicios que utilizan la configuración
    export namespace Servicios {
        // Uso de elementos del namespace hermano
        export class ApiCliente {
            constructor() {
                console.log(`Inicializando API con URL: ${Config.API_URL}`);
                console.log(`Timeout configurado: ${Config.TIMEOUT}ms`);
            }
            
            obtenerDatos(): Promise<any> {
                return fetch(Config.API_URL, {
                    signal: AbortSignal.timeout(Config.TIMEOUT)
                }).then(res => res.json());
            }
        }
    }
}

// Uso del servicio
const cliente = new Sistema.Servicios.ApiCliente();

Uso de alias para simplificar el acceso

Cuando trabajamos con namespaces anidados profundos, la notación de punto puede volverse tediosa. TypeScript permite crear alias para simplificar el acceso a estos elementos:

namespace Organizacion {
    export namespace Departamentos {
        export namespace RecursosHumanos {
            export class Empleado {
                constructor(public nombre: string, public puesto: string) {}
                
                mostrarInfo(): string {
                    return `${this.nombre} - ${this.puesto}`;
                }
            }
        }
    }
}

// Creación de alias para simplificar el acceso
import EmpleadoRH = Organizacion.Departamentos.RecursosHumanos.Empleado;

// Uso del alias
const empleado = new EmpleadoRH("Laura Martínez", "Desarrolladora Senior");
console.log(empleado.mostrarInfo()); // "Laura Martínez - Desarrolladora Senior"

Fusión de namespaces anidados

Una característica poderosa de los namespaces en TypeScript es la capacidad de fusión de declaraciones. Podemos extender namespaces anidados existentes en diferentes partes del código:

// Primera declaración
namespace Proyecto {
    export namespace Componentes {
        export class Boton {
            constructor(public texto: string) {}
            
            renderizar(): string {
                return `<button>${this.texto}</button>`;
            }
        }
    }
}

// Extensión del namespace anidado en otra parte del código
namespace Proyecto {
    export namespace Componentes {
        export class Enlace {
            constructor(public texto: string, public url: string) {}
            
            renderizar(): string {
                return `<a href="${this.url}">${this.texto}</a>`;
            }
        }
    }
}

// Ambos componentes están disponibles
const boton = new Proyecto.Componentes.Boton("Enviar");
const enlace = new Proyecto.Componentes.Enlace("Visitar sitio", "https://ejemplo.com");

console.log(boton.renderizar());  // <button>Enviar</button>
console.log(enlace.renderizar()); // <a href="https://ejemplo.com">Visitar sitio</a>

Patrones de organización con namespaces anidados

Los namespaces anidados facilitan la implementación de patrones de organización comunes:

Patrón de módulo de funcionalidad

Este patrón agrupa funcionalidades relacionadas en un namespace anidado específico:

namespace App {
    // Namespace para funcionalidades de autenticación
    export namespace Auth {
        export interface Usuario {
            id: string;
            nombre: string;
            roles: string[];
        }
        
        export class Autenticador {
            usuarioActual: Usuario | null = null;
            
            iniciarSesion(usuario: string, contraseña: string): boolean {
                // Lógica de autenticación
                this.usuarioActual = {
                    id: "usr123",
                    nombre: usuario,
                    roles: ["usuario"]
                };
                return true;
            }
            
            cerrarSesion(): void {
                this.usuarioActual = null;
            }
        }
    }
    
    // Namespace para funcionalidades de UI
    export namespace UI {
        export class LoginForm {
            private auth = new Auth.Autenticador();
            
            procesarLogin(evento: Event): void {
                evento.preventDefault();
                // Lógica para obtener valores del formulario
                const exito = this.auth.iniciarSesion("usuario", "contraseña");
                if (exito) {
                    console.log("Sesión iniciada correctamente");
                }
            }
        }
    }
}

Patrón de capas de aplicación

Este patrón organiza el código en capas lógicas, como datos, servicios y presentación:

namespace Tienda {
    // Capa de modelos/entidades
    export namespace Modelos {
        export interface Producto {
            id: number;
            nombre: string;
            precio: number;
            stock: number;
        }
        
        export interface Cliente {
            id: number;
            nombre: string;
            email: string;
        }
    }
    
    // Capa de acceso a datos
    export namespace Datos {
        export class ProductoRepositorio {
            obtenerProductos(): Modelos.Producto[] {
                // Simulación de acceso a base de datos
                return [
                    { id: 1, nombre: "Laptop", precio: 999, stock: 10 },
                    { id: 2, nombre: "Smartphone", precio: 699, stock: 15 }
                ];
            }
        }
    }
    
    // Capa de servicios de negocio
    export namespace Servicios {
        export class CarritoServicio {
            private productos: Modelos.Producto[] = [];
            
            agregarProducto(producto: Modelos.Producto, cantidad: number = 1): void {
                if (producto.stock >= cantidad) {
                    this.productos.push({...producto, stock: cantidad});
                    console.log(`${cantidad} ${producto.nombre}(s) agregado(s) al carrito`);
                } else {
                    console.log(`Stock insuficiente para ${producto.nombre}`);
                }
            }
            
            calcularTotal(): number {
                return this.productos.reduce((total, p) => total + (p.precio * p.stock), 0);
            }
        }
    }
}

// Uso del patrón de capas
const repo = new Tienda.Datos.ProductoRepositorio();
const carrito = new Tienda.Servicios.CarritoServicio();

const productos = repo.obtenerProductos();
carrito.agregarProducto(productos[0], 2);
console.log(`Total del carrito: $${carrito.calcularTotal()}`);

Consideraciones de rendimiento

Al trabajar con namespaces anidados, es importante considerar algunas implicaciones de rendimiento:

  • Los namespaces anidados profundos pueden generar código JavaScript con cadenas de acceso largas, lo que podría afectar ligeramente al rendimiento.
  • Para optimizar el código generado, considera usar alias para los namespaces más utilizados.
  • En aplicaciones grandes, evalúa si la división en módulos podría ser más eficiente que los namespaces anidados muy profundos.
// Optimización mediante alias
namespace A {
    export namespace B {
        export namespace C {
            export namespace D {
                export function procesar(): void {
                    console.log("Procesando...");
                }
            }
        }
    }
}

// Crear alias para optimizar
import procesador = A.B.C.D.procesar;

// Uso más eficiente
procesador();

Los namespaces anidados son una herramienta poderosa para organizar código en aplicaciones TypeScript, especialmente en proyectos grandes o cuando se trabaja con librerías extensas. Sin embargo, para proyectos nuevos, considera evaluar si el sistema de módulos ES podría ser más adecuado para tus necesidades específicas.

Ambient namespaces

Los ambient namespaces son una característica especial de TypeScript que permite declarar la estructura de librerías o módulos externos escritos en JavaScript, sin proporcionar una implementación real. Funcionan como una forma de describir la forma y los tipos de código que existe en otro lugar, generalmente en bibliotecas de terceros que no fueron escritas originalmente en TypeScript.

Cuando trabajamos con bibliotecas JavaScript que no incluyen definiciones de tipos, los ambient namespaces nos permiten crear una "envoltura tipada" alrededor de ese código, proporcionando así todas las ventajas del sistema de tipos de TypeScript sin necesidad de reescribir la biblioteca.

Declaración de ambient namespaces

Para crear un ambient namespace, utilizamos la palabra clave declare junto con namespace:

declare namespace JQuery {
    function ajax(url: string, settings?: any): void;
    function get(url: string, callback: (data: any) => void): void;
    
    interface AjaxSettings {
        method?: string;
        data?: any;
        contentType?: string;
        success?: (data: any, status: string) => void;
        error?: (xhr: any) => void;
    }
}

Este ejemplo declara un ambient namespace para una versión simplificada de jQuery, definiendo algunos de sus métodos y tipos sin proporcionar la implementación real.

Archivos de declaración (.d.ts)

Los ambient namespaces suelen definirse en archivos de declaración con extensión .d.ts. Estos archivos contienen solo información de tipos y no código ejecutable:

jquery.d.ts:

declare namespace JQuery {
    function ajax(url: string, settings?: AjaxSettings): void;
    
    interface AjaxSettings {
        method?: string;
        data?: any;
        contentType?: string;
        success?: (data: any, status: string) => void;
        error?: (xhr: any) => void;
    }
}

// Variable global $ como alias para JQuery
declare const $: typeof JQuery;

Una vez definido este archivo de declaración, podemos utilizar la biblioteca jQuery con comprobación de tipos completa:

// TypeScript reconoce los tipos gracias al ambient namespace
$.ajax("https://api.ejemplo.com/datos", {
    method: "POST",
    data: { id: 123 },
    success: (respuesta, estado) => {
        console.log(`Datos recibidos: ${JSON.stringify(respuesta)}`);
    },
    error: (xhr) => {
        console.error("Error en la petición");
    }
});

Ambient namespaces para librerías globales

Muchas bibliotecas JavaScript antiguas exponen sus funcionalidades como variables globales. Los ambient namespaces son ideales para tipar estas bibliotecas:

// Definición para una biblioteca de gráficos ficticia
declare namespace ChartLibrary {
    interface ChartOptions {
        width?: number;
        height?: number;
        title?: string;
        colors?: string[];
    }
    
    interface ChartInstance {
        update(data: number[]): void;
        resize(width: number, height: number): void;
        destroy(): void;
    }
    
    function createBarChart(element: string | HTMLElement, data: number[], options?: ChartOptions): ChartInstance;
    function createLineChart(element: string | HTMLElement, data: number[], options?: ChartOptions): ChartInstance;
}

// Variable global
declare const Chart: typeof ChartLibrary;

Con esta definición, podemos usar la biblioteca en nuestro código TypeScript:

// Crear un gráfico de barras
const barChart = Chart.createBarChart("#grafico", [10, 20, 30, 25, 15], {
    width: 600,
    height: 400,
    title: "Ventas mensuales",
    colors: ["#3498db", "#2ecc71", "#e74c3c"]
});

// Actualizar datos
barChart.update([15, 25, 35, 30, 20]);

// Redimensionar
barChart.resize(800, 500);

Ambient namespaces anidados

Al igual que los namespaces regulares, los ambient namespaces pueden anidarse para representar estructuras jerárquicas:

declare namespace App {
    namespace Utils {
        function formatCurrency(value: number, currency?: string): string;
        function formatDate(date: Date, format?: string): string;
    }
    
    namespace Components {
        class DataTable {
            constructor(element: string | HTMLElement, options?: DataTableOptions);
            refresh(): void;
            sort(column: string, direction: 'asc' | 'desc'): void;
        }
        
        interface DataTableOptions {
            columns: Array<{name: string, title: string}>;
            pageSize?: number;
            sortable?: boolean;
        }
    }
}

Fusión de declaraciones con ambient namespaces

Una característica poderosa de los ambient namespaces es la capacidad de fusionar declaraciones. Podemos extender definiciones existentes en diferentes archivos:

base-library.d.ts:

declare namespace MiLibreria {
    function inicializar(config: any): void;
    
    interface ConfiguracionBase {
        debug: boolean;
        version: string;
    }
}

extension-library.d.ts:

// Extender el namespace existente
declare namespace MiLibreria {
    // Añadir nuevas funciones
    function terminar(): void;
    
    // Extender interfaces existentes
    interface ConfiguracionBase {
        timeout?: number;
        retryCount?: number;
    }
    
    // Añadir nuevas interfaces
    interface PluginConfig {
        nombre: string;
        habilitado: boolean;
    }
    
    function registrarPlugin(plugin: PluginConfig): void;
}

Ahora podemos usar todas las funcionalidades combinadas:

MiLibreria.inicializar({
    debug: true,
    version: "1.0.0",
    timeout: 5000,  // De la extensión
    retryCount: 3   // De la extensión
});

MiLibreria.registrarPlugin({
    nombre: "DataSync",
    habilitado: true
});

MiLibreria.terminar();

Ambient namespaces vs. declare module

TypeScript ofrece dos enfoques principales para declarar tipos para código externo:

  1. Ambient namespaces (declare namespace): Ideal para bibliotecas globales tradicionales.
  2. Declare module (declare module): Mejor para bibliotecas basadas en módulos.
// Enfoque con ambient namespace (para bibliotecas globales)
declare namespace Moment {
    function format(date: Date, format: string): string;
    function now(): Date;
}

// Enfoque con declare module (para bibliotecas basadas en módulos)
declare module 'moment' {
    function format(date: Date, format: string): string;
    function now(): Date;
    export = Moment;
}

La elección entre estos enfoques depende de cómo se consume la biblioteca:

  • Usa ambient namespaces para bibliotecas que se cargan mediante etiquetas <script> y exponen variables globales.
  • Usa declare module para bibliotecas que se importan mediante sistemas de módulos como CommonJS o ES modules.

Uso con DefinitelyTyped

En la práctica, rara vez necesitarás escribir ambient namespaces desde cero. La comunidad de TypeScript mantiene el repositorio DefinitelyTyped que contiene definiciones de tipos para miles de bibliotecas JavaScript populares.

Puedes instalar estas definiciones usando npm con el prefijo @types/:

npm install --save-dev @types/jquery

Una vez instaladas, TypeScript reconocerá automáticamente los tipos para esa biblioteca:

// No es necesario importar nada para bibliotecas globales
$('#elemento').fadeIn(400);

// Para bibliotecas basadas en módulos
import * as moment from 'moment';
const fechaFormateada = moment().format('YYYY-MM-DD');

Creación de ambient namespaces personalizados

A veces necesitarás crear ambient namespaces para código personalizado o bibliotecas sin definiciones de tipos disponibles:

// mi-libreria.d.ts
declare namespace Analytics {
    function iniciarSesion(userId: string, properties?: Record<string, any>): void;
    function registrarEvento(nombre: string, propiedades?: Record<string, any>): void;
    function establecerPropiedad(clave: string, valor: any): void;
    
    interface ConfiguracionAnalytics {
        appId: string;
        debug?: boolean;
        samplingRate?: number;
    }
    
    function inicializar(config: ConfiguracionAnalytics): void;
}

Mejores prácticas para ambient namespaces

Al trabajar con ambient namespaces, considera estas recomendaciones:

  • Sé específico con los tipos: Evita usar any cuando sea posible, define interfaces para estructuras de datos complejas.
  • Documenta tus declaraciones: Añade comentarios JSDoc para proporcionar información adicional sobre los métodos y parámetros.
  • Mantén la compatibilidad: Asegúrate de que tus definiciones coincidan con el comportamiento real de la biblioteca.
  • Divide declaraciones grandes: Para bibliotecas extensas, considera dividir las definiciones en múltiples archivos.
// Ejemplo con buenas prácticas
declare namespace Analítica {
    /**
     * Inicializa el sistema de analítica con la configuración proporcionada.
     * @param config Configuración para el sistema de analítica
     * @returns Un identificador único para la sesión
     */
    function inicializar(config: ConfigAnalítica): string;
    
    /**
     * Configuración para el sistema de analítica
     */
    interface ConfigAnalítica {
        /** ID único de la aplicación */
        appId: string;
        /** Habilita mensajes de depuración en consola */
        debug?: boolean;
        /** Tasa de muestreo entre 0 y 1 */
        tasaMuestreo?: number;
    }
}

Los ambient namespaces son una herramienta fundamental para integrar código JavaScript existente en proyectos TypeScript, proporcionando seguridad de tipos sin necesidad de reescribir o modificar el código original. Aunque para proyectos nuevos se recomienda el uso de módulos ES, los ambient namespaces siguen siendo esenciales para trabajar con el vasto ecosistema de bibliotecas JavaScript existentes.

Módulos vs namespaces

TypeScript ofrece dos mecanismos principales para organizar y estructurar el código: namespaces y módulos. Aunque ambos sirven para evitar la contaminación del ámbito global y organizar el código, presentan diferencias fundamentales en su enfoque, implementación y casos de uso recomendados.

Conceptos fundamentales

Los namespaces (anteriormente llamados "módulos internos") son una característica específica de TypeScript que proporciona una forma de agrupar código relacionado bajo un mismo nombre. Por otro lado, los módulos (antes conocidos como "módulos externos") siguen el estándar ECMAScript y son compatibles con los sistemas de módulos de JavaScript moderno.

// Ejemplo de namespace
namespace Geometria {
    export function calcularArea(radio: number): number {
        return Math.PI * radio * radio;
    }
}

// Ejemplo de módulo (en archivo geometria.ts)
export function calcularArea(radio: number): number {
    return Math.PI * radio * radio;
}

Diferencias en la sintaxis y uso

La diferencia más evidente entre ambos enfoques está en cómo se declaran y cómo se accede a sus miembros:

Namespaces:

  • Se declaran con la palabra clave namespace
  • Se accede a sus miembros mediante la notación de punto
  • Requieren la palabra clave export para exponer miembros
namespace Utilidades {
    export function formatearFecha(fecha: Date): string {
        return fecha.toISOString().split('T')[0];
    }
}

// Uso del namespace
const fechaFormateada = Utilidades.formatearFecha(new Date());

Módulos:

  • Se definen en archivos separados
  • Se importan/exportan mediante las palabras clave import/export
  • Cada archivo es implícitamente un módulo
// utilidades.ts
export function formatearFecha(fecha: Date): string {
    return fecha.toISOString().split('T')[0];
}

// app.ts
import { formatearFecha } from './utilidades';

const fechaFormateada = formatearFecha(new Date());

Resolución y carga

Una diferencia crucial entre namespaces y módulos es cómo se resuelven y cargan:

Namespaces:

  • Se combinan en tiempo de compilación
  • No tienen un sistema de resolución de dependencias integrado
  • Requieren referencias de triple barra oblicua (///) para combinar archivos o incluir todos los archivos en la compilación
// matematicas-base.ts
namespace Matematicas {
    export const PI = 3.14159;
}

// matematicas-funciones.ts
/// <reference path="matematicas-base.ts" />
namespace Matematicas {
    export function calcularCircunferencia(radio: number): number {
        return 2 * PI * radio;
    }
}

Módulos:

  • Se resuelven en tiempo de ejecución (o durante el empaquetado)
  • Utilizan un sistema de resolución de módulos (Node.js, AMD, UMD, ES)
  • Las dependencias se declaran explícitamente mediante importaciones
// constantes.ts
export const PI = 3.14159;

// funciones.ts
import { PI } from './constantes';

export function calcularCircunferencia(radio: number): number {
    return 2 * PI * radio;
}

Empaquetado y herramientas modernas

El ecosistema de desarrollo moderno favorece claramente a los módulos sobre los namespaces:

Namespaces:

  • Requieren configuración especial para funcionar con empaquetadores como Webpack o Rollup
  • No son compatibles de forma nativa con la carga dinámica de código
  • Generan código JavaScript menos optimizable por herramientas modernas

Módulos:

  • Compatibilidad nativa con empaquetadores (Webpack, Rollup, Parcel)
  • Soporte para importaciones dinámicas (import())
  • Mejor tree-shaking (eliminación de código no utilizado)
// Ejemplo de importación dinámica (solo posible con módulos)
async function cargarComponente() {
    const { Componente } = await import('./componentes/lazy-componente');
    return new Componente();
}

Aislamiento y encapsulación

Ambos mecanismos proporcionan encapsulación, pero con diferencias importantes:

Namespaces:

  • Todo el código no exportado está encapsulado dentro del namespace
  • Los namespaces pueden fusionarse, lo que puede romper el aislamiento
  • Comparten el mismo ámbito global para variables

Módulos:

  • Cada módulo tiene su propio ámbito aislado
  • Las variables son locales al módulo por defecto
  • Solo se expone lo que se exporta explícitamente
  • No se pueden fusionar, lo que garantiza mejor aislamiento
// Con namespaces, las declaraciones se fusionan
namespace Configuracion {
    export const DEBUG = true;
}

namespace Configuracion {
    export const VERSION = "1.0.0"; // Se fusiona con la declaración anterior
}

// Con módulos, cada archivo es independiente
// config.ts
export const DEBUG = true;

// version.ts
export const VERSION = "1.0.0"; // No hay fusión, son módulos separados

Rendimiento y tamaño del código

El enfoque elegido puede afectar al rendimiento y tamaño del código generado:

Namespaces:

  • Generan objetos anidados en JavaScript
  • Pueden producir código más verboso
  • No permiten optimizaciones avanzadas como tree-shaking
// JavaScript generado para un namespace
var Geometria;
(function (Geometria) {
    function calcularArea(radio) {
        return Math.PI * radio * radio;
    }
    Geometria.calcularArea = calcularArea;
})(Geometria || (Geometria = {}));

Módulos:

  • Generan código más limpio y directo
  • Permiten mejor optimización por parte de empaquetadores
  • Facilitan la eliminación de código no utilizado
// JavaScript generado para un módulo (simplificado)
export function calcularArea(radio) {
    return Math.PI * radio * radio;
}

Compatibilidad con JavaScript moderno

La evolución de JavaScript ha favorecido claramente el enfoque de módulos:

Namespaces:

  • Son una característica específica de TypeScript
  • No tienen equivalente directo en JavaScript estándar
  • Requieren transpilación para funcionar en navegadores

Módulos:

  • Son parte del estándar ECMAScript (ES2015+)
  • Compatibles nativamente con navegadores modernos
  • Alineados con la dirección futura de JavaScript

Casos de uso recomendados

Basándonos en las diferencias anteriores, podemos establecer recomendaciones claras:

Usar namespaces cuando:

  • Trabajas con código legacy que ya utiliza namespaces
  • Necesitas agrupar declaraciones de tipos ambient (.d.ts)
  • Desarrollas una aplicación simple sin herramientas de empaquetado
// Caso de uso adecuado para namespaces: declaraciones ambient
declare namespace JQuery {
    function ajax(url: string, settings?: any): void;
    // ...
}

Usar módulos cuando:

  • Desarrollas aplicaciones nuevas
  • Utilizas herramientas modernas como webpack, Rollup o Parcel
  • Necesitas cargar código bajo demanda
  • Trabajas en equipos con diferentes niveles de experiencia en TypeScript
// Caso de uso adecuado para módulos: aplicación moderna
// api.ts
export async function fetchData<T>(url: string): Promise<T> {
    const response = await fetch(url);
    return response.json();
}

// app.ts
import { fetchData } from './api';

async function inicializar() {
    const datos = await fetchData<Usuario[]>('/api/usuarios');
    console.log(datos);
}

Migración de namespaces a módulos

Si estás trabajando en un proyecto que utiliza namespaces y deseas migrar a módulos, puedes seguir estos pasos:

  1. Identificar los namespaces a migrar
  2. Crear archivos separados para cada namespace
  3. Convertir las exportaciones del namespace a exportaciones de módulo
  4. Actualizar las referencias en todo el código
// Antes: con namespace
namespace Validacion {
    export interface Validador {
        esValido(s: string): boolean;
    }
    
    export class ValidadorEmail implements Validador {
        esValido(s: string): boolean {
            return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
        }
    }
}

// Después: con módulos
// validador.ts
export interface Validador {
    esValido(s: string): boolean;
}

// validadorEmail.ts
import { Validador } from './validador';

export class ValidadorEmail implements Validador {
    esValido(s: string): boolean {
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
    }
}

Interoperabilidad entre namespaces y módulos

En proyectos grandes o durante migraciones, puede ser necesario que namespaces y módulos coexistan:

// Definir un namespace
namespace Utilidades {
    export function formatear(valor: string): string {
        return valor.trim().toLowerCase();
    }
}

// Exportar el namespace como un módulo
export = Utilidades;

// En otro archivo, importar el namespace como un módulo
import Utilidades = require('./utilidades');
const texto = Utilidades.formatear("  EJEMPLO  ");

Consideraciones para equipos y proyectos

La elección entre namespaces y módulos también debe considerar factores organizativos:

  • Curva de aprendizaje: Los módulos siguen patrones estándar de JavaScript, lo que facilita la incorporación de nuevos desarrolladores.
  • Mantenibilidad a largo plazo: Los módulos están mejor alineados con la evolución de JavaScript y TypeScript.
  • Herramientas de desarrollo: Los IDEs y editores ofrecen mejor soporte para módulos (autocompletado, navegación, refactorización).
  • Escalabilidad: Los módulos facilitan la división del código en componentes más pequeños y manejables.
// Estructura de proyecto con módulos
// src/
//   ├── modelos/
//   │   ├── usuario.ts
//   │   └── producto.ts
//   ├── servicios/
//   │   ├── autenticacion.ts
//   │   └── api.ts
//   └── componentes/
//       ├── login.ts
//       └── dashboard.ts

En resumen, aunque los namespaces siguen siendo una característica válida de TypeScript, los módulos representan el enfoque recomendado para la mayoría de los proyectos modernos. Los módulos ofrecen mejor compatibilidad con el ecosistema JavaScript actual, mejores herramientas de optimización y un modelo mental más claro para los desarrolladores familiarizados con JavaScript moderno.

Aprende TypeScript online

Otras lecciones de TypeScript

Accede a todas las lecciones de TypeScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Typescript

TypeScript

Introducción Y Entorno

Instalación Y Configuración De Typescript

TypeScript

Introducción Y Entorno

Tipos De Datos, Variables Y Constantes

TypeScript

Sintaxis

Operadores Y Expresiones

TypeScript

Sintaxis

Control De Flujo

TypeScript

Sintaxis

Funciones Y Parámetros Tipados

TypeScript

Sintaxis

Funciones Flecha Y Contexto

TypeScript

Sintaxis

Enums

TypeScript

Sintaxis

Type Aliases Y Aserciones De Tipo

TypeScript

Sintaxis

Clases Y Objetos

TypeScript

Programación Orientada A Objetos

Interfaces Y Su Implementación

TypeScript

Programación Orientada A Objetos

Modificadores De Acceso Y Encapsulación

TypeScript

Programación Orientada A Objetos

Herencia Y Clases Abstractas

TypeScript

Programación Orientada A Objetos

Polimorfismo

TypeScript

Programación Orientada A Objetos

Decoradores Básicos

TypeScript

Programación Orientada A Objetos

Propiedades Y Métodos

TypeScript

Programación Orientada A Objetos

Inmutabilidad

TypeScript

Programación Funcional

Funciones Puras Y Efectos Secundarios

TypeScript

Programación Funcional

Funciones De Primera Clase

TypeScript

Programación Funcional

Funciones De Alto Orden

TypeScript

Programación Funcional

Conceptos Básicos E Inmutabilidad

TypeScript

Programación Funcional

Funciones De Primera Clase Y Orden Superior

TypeScript

Programación Funcional

Composición De Funciones

TypeScript

Programación Funcional

Métodos Funcionales De Arrays (Map, Filter, Reduce)

TypeScript

Programación Funcional

Tipos Literales Y Tipos Condicionales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Genéricos Básicos

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Unión E Intersección

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Utilidad (Partial, Required, Pick, Etc)

TypeScript

Tipos Intermedios Y Avanzados

Unknown, Never Y Tipos Especiales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Mapped

TypeScript

Tipos Intermedios Y Avanzados

Genéricos Con Clases E Interfaces

TypeScript

Tipos Intermedios Y Avanzados

Módulos

TypeScript

Namespaces Y Módulos

Namespaces

TypeScript

Namespaces Y Módulos

Resolución De Módulos

TypeScript

Namespaces Y Módulos

Exportación E Importación De Módulos

TypeScript

Namespaces Y Módulos

Introducción A Módulos

TypeScript

Namespaces Y Módulos

Testing Unitario En Typescript

TypeScript

Testing

Accede GRATIS a TypeScript y certifícate

Ejercicios de programación de TypeScript

Evalúa tus conocimientos de esta lección Namespaces con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Funciones

TypeScript
Test

Reto composición de funciones

TypeScript
Código

Reto tipos especiales

TypeScript
Código

Reto tipos genéricos

TypeScript
Código

Módulos

TypeScript
Test

Polimorfismo

TypeScript
Código

Funciones TypeScript

TypeScript
Código

Interfaces

TypeScript
Puzzle

Funciones puras

TypeScript
Puzzle

Reto namespaces

TypeScript
Código

Funciones flecha

TypeScript
Puzzle

Polimorfismo

TypeScript
Test

Operadores

TypeScript
Test

Conversor de unidades

TypeScript
Proyecto

Funciones flecha

TypeScript
Test

Control de flujo

TypeScript
Código

Herencia

TypeScript
Puzzle

Clases

TypeScript
Puzzle

Proyecto validación de tipado

TypeScript
Proyecto

Clases y objetos

TypeScript
Código

Encapsulación

TypeScript
Test

Herencia

TypeScript
Test

Proyecto sistema de votación

TypeScript
Proyecto

Reto genéricos con clases

TypeScript
Código

Inmutabilidad

TypeScript
Puzzle

Interfaces

TypeScript
Test

Funciones de alto orden

TypeScript
Test

Reto map y filter

TypeScript
Código

Control de flujo

TypeScript
Test

Interfaces

TypeScript
Código

Reto funciones orden superior

TypeScript
Código

Herencia y clases abstractas

TypeScript
Código

Reto tipos mapped

TypeScript
Código

Herencia de clases

TypeScript
Código

Reto funciones puras

TypeScript
Código

Variables y constantes

TypeScript
Puzzle

Introducción a TypeScript

TypeScript
Test

Reto testing unitario

TypeScript
Código

Funciones de primera clase

TypeScript
Puzzle

Clases

TypeScript
Test

OOP y CRUD en TypeScript

TypeScript
Proyecto

Interfaces y su implementación

TypeScript
Código

Tipos genéricos

TypeScript
Test

Namespaces

TypeScript
Test

Operadores y expresiones

TypeScript
Código

Proyecto generador de contraseñas

TypeScript
Proyecto

Reto unión e intersección

TypeScript
Código

Encapsulación

TypeScript
Puzzle

Tipos de unión e intersección

TypeScript
Test

Tipos de unión e intersección

TypeScript
Puzzle

Reto hola mundo en TS

TypeScript
Código

Variables y constantes

TypeScript
Código

Funciones puras

TypeScript
Test

Control de flujo

TypeScript
Código

Introducción a TypeScript

TypeScript
Código

Resolución de módulos

TypeScript
Test

Control de flujo

TypeScript
Puzzle

Reto tipos de utilidad

TypeScript
Código

Reto tipos literales y condicionales

TypeScript
Código

Reto exportar e importar

TypeScript
Código

Propiedades y métodos

TypeScript
Código

Tipos de utilidad

TypeScript
Test

Clases y objetos

TypeScript
Código

Tipos de datos, variables y constantes

TypeScript
Código

Proyecto Minigestor de tareas

TypeScript
Proyecto

Operadores

TypeScript
Puzzle

Funciones flecha y contexto

TypeScript
Código

Proyecto Inventario de productos

TypeScript
Proyecto

Funciones

TypeScript
Puzzle

Reto type aliases

TypeScript
Código

Funciones de alto orden

TypeScript
Puzzle

Funciones y parámetros tipados

TypeScript
Código

Tipos literales

TypeScript
Puzzle

Reto enums

TypeScript
Código

Tipos de utilidad

TypeScript
Puzzle

Modificadores de acceso y encapsulación

TypeScript
Código

Polimorfismo

TypeScript
Puzzle

Tipos genéricos

TypeScript
Puzzle

Reto módulos

TypeScript
Código

Tipos literales

TypeScript
Test

Inmutabilidad

TypeScript
Test

Proyecto Generator de datos

TypeScript
Proyecto

Variables y constantes

TypeScript
Test

Funciones de primera clase

TypeScript
Test

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la declaración y uso básico de namespaces en TypeScript.
  • Aprender a exportar e importar elementos dentro de namespaces.
  • Conocer cómo organizar código con namespaces anidados y su uso práctico.
  • Entender qué son los ambient namespaces y su utilidad para tipar librerías externas.
  • Diferenciar entre namespaces y módulos, y conocer cuándo usar cada uno.