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ícateDeclaració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:
- Ambient namespaces (
declare namespace
): Ideal para bibliotecas globales tradicionales. - 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:
- Identificar los namespaces a migrar
- Crear archivos separados para cada namespace
- Convertir las exportaciones del namespace a exportaciones de módulo
- 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.
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
Introducción Y Entorno
Instalación Y Configuración De Typescript
Introducción Y Entorno
Tipos De Datos, Variables Y Constantes
Sintaxis
Operadores Y Expresiones
Sintaxis
Control De Flujo
Sintaxis
Funciones Y Parámetros Tipados
Sintaxis
Funciones Flecha Y Contexto
Sintaxis
Enums
Sintaxis
Type Aliases Y Aserciones De Tipo
Sintaxis
Clases Y Objetos
Programación Orientada A Objetos
Interfaces Y Su Implementación
Programación Orientada A Objetos
Modificadores De Acceso Y Encapsulación
Programación Orientada A Objetos
Herencia Y Clases Abstractas
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Decoradores Básicos
Programación Orientada A Objetos
Propiedades Y Métodos
Programación Orientada A Objetos
Inmutabilidad
Programación Funcional
Funciones Puras Y Efectos Secundarios
Programación Funcional
Funciones De Primera Clase
Programación Funcional
Funciones De Alto Orden
Programación Funcional
Conceptos Básicos E Inmutabilidad
Programación Funcional
Funciones De Primera Clase Y Orden Superior
Programación Funcional
Composición De Funciones
Programación Funcional
Métodos Funcionales De Arrays (Map, Filter, Reduce)
Programación Funcional
Tipos Literales Y Tipos Condicionales
Tipos Intermedios Y Avanzados
Tipos Genéricos Básicos
Tipos Intermedios Y Avanzados
Tipos De Unión E Intersección
Tipos Intermedios Y Avanzados
Tipos De Utilidad (Partial, Required, Pick, Etc)
Tipos Intermedios Y Avanzados
Unknown, Never Y Tipos Especiales
Tipos Intermedios Y Avanzados
Tipos Mapped
Tipos Intermedios Y Avanzados
Genéricos Con Clases E Interfaces
Tipos Intermedios Y Avanzados
Módulos
Namespaces Y Módulos
Namespaces
Namespaces Y Módulos
Resolución De Módulos
Namespaces Y Módulos
Exportación E Importación De Módulos
Namespaces Y Módulos
Introducción A Módulos
Namespaces Y Módulos
Testing Unitario En Typescript
Testing
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
Reto composición de funciones
Reto tipos especiales
Reto tipos genéricos
Módulos
Polimorfismo
Funciones TypeScript
Interfaces
Funciones puras
Reto namespaces
Funciones flecha
Polimorfismo
Operadores
Conversor de unidades
Funciones flecha
Control de flujo
Herencia
Clases
Proyecto validación de tipado
Clases y objetos
Encapsulación
Herencia
Proyecto sistema de votación
Reto genéricos con clases
Inmutabilidad
Interfaces
Funciones de alto orden
Reto map y filter
Control de flujo
Interfaces
Reto funciones orden superior
Herencia y clases abstractas
Reto tipos mapped
Herencia de clases
Reto funciones puras
Variables y constantes
Introducción a TypeScript
Reto testing unitario
Funciones de primera clase
Clases
OOP y CRUD en TypeScript
Interfaces y su implementación
Tipos genéricos
Namespaces
Operadores y expresiones
Proyecto generador de contraseñas
Reto unión e intersección
Encapsulación
Tipos de unión e intersección
Tipos de unión e intersección
Reto hola mundo en TS
Variables y constantes
Funciones puras
Control de flujo
Introducción a TypeScript
Resolución de módulos
Control de flujo
Reto tipos de utilidad
Reto tipos literales y condicionales
Reto exportar e importar
Propiedades y métodos
Tipos de utilidad
Clases y objetos
Tipos de datos, variables y constantes
Proyecto Minigestor de tareas
Operadores
Funciones flecha y contexto
Proyecto Inventario de productos
Funciones
Reto type aliases
Funciones de alto orden
Funciones y parámetros tipados
Tipos literales
Reto enums
Tipos de utilidad
Modificadores de acceso y encapsulación
Polimorfismo
Tipos genéricos
Reto módulos
Tipos literales
Inmutabilidad
Proyecto Generator de datos
Variables y constantes
Funciones de primera clase
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.