
Declaración de interfaces
Una interface en TypeScript define la forma que debe tener un objeto. Actua como un contrato que específica que propiedades y métodos debe contener un valor para ser compatible con esa interfaz. Las interfaces son una herramienta exclusiva del sistema de tipos: no generan código JavaScript y solo existen durante la compilación.
interface Usuario {
nombre: string
edad: number
email: string
}
const usuario: Usuario = {
nombre: "Ana",
edad: 28,
email: "ana@mail.com"
}
console.log(usuario.nombre) // "Ana"
console.log(usuario.edad) // 28
Las interfaces se basan en el tipado estructural de TypeScript. Un objeto es compatible con una interfaz si tiene al menos las propiedades requeridas con los tipos correctos, independientemente de como se haya creado.
Las interfaces pueden usarse para tipar parámetros de función, valores de retorno y variables:
interface Producto {
nombre: string
precio: number
stock: number
}
function mostrarProducto(producto: Producto): string {
return `${producto.nombre}: ${producto.precio} EUR (${producto.stock} unidades)`
}
const laptop: Producto = { nombre: "Laptop", precio: 999, stock: 15 }
console.log(mostrarProducto(laptop))
// "Laptop: 999 EUR (15 unidades)"
Propiedades opcionales
Las propiedades opcionales se marcan con ? después del nombre. El objeto puede incluirlas o no:
interface Configuración {
host: string
puerto: number
ssl?: boolean
timeout?: number
}
const configMinima: Configuración = {
host: "localhost",
puerto: 3000
}
const configCompleta: Configuración = {
host: "api.ejemplo.com",
puerto: 443,
ssl: true,
timeout: 5000
}
function describir(config: Configuración): string {
let descripcion = `${config.host}:${config.puerto}`
if (config.ssl) {
descripcion += " (SSL)"
}
if (config.timeout) {
descripcion += ` timeout=${config.timeout}ms`
}
return descripcion
}
console.log(describir(configMinima)) // "localhost:3000"
console.log(describir(configCompleta)) // "api.ejemplo.com:443 (SSL) timeout=5000ms"
Propiedades de solo lectura
La palabra clave readonly impide que una propiedad se modifique después de la asignación inicial:
interface Punto {
readonly x: number
readonly y: number
}
const origen: Punto = { x: 0, y: 0 }
console.log(origen.x) // 0
// Error: Cannot assign to 'x' because it is a read-only property
// origen.x = 10
interface Documento {
readonly id: string
titulo: string
contenido: string
}
const doc: Documento = {
id: "doc-001",
titulo: "Borrador",
contenido: "Contenido inicial"
}
doc.titulo = "Versión final" // Permitido
doc.contenido = "Nuevo texto" // Permitido
// doc.id = "doc-002" // Error: readonly
La marca
readonlysolo afecta a la propiedad directa. Si la propiedad es un objeto, sus propiedades internas siguen siendo mutables a menos que también se marquen comoreadonly.
interface Equipo {
readonly nombre: string
readonly miembros: string[]
}
const equipo: Equipo = {
nombre: "Backend",
miembros: ["Ana", "Carlos"]
}
// Error: readonly en la propiedad directa
// equipo.nombre = "Frontend"
// Permitido: el contenido del array no está protegido por readonly
equipo.miembros.push("Elena")
console.log(equipo.miembros) // ["Ana", "Carlos", "Elena"]
Extensión de interfaces con extends
Las interfaces pueden extender otras interfaces para heredar sus propiedades y añadir nuevas. Esto permite componer tipos complejos a partir de tipos más simples.
interface Entidad {
id: number
creadoEn: Date
}
interface Persona extends Entidad {
nombre: string
email: string
}
interface Empleado extends Persona {
departamento: string
salario: number
}
const empleado: Empleado = {
id: 1,
creadoEn: new Date(),
nombre: "Carlos",
email: "carlos@empresa.com",
departamento: "Ingenieria",
salario: 45000
}
console.log(empleado.nombre) // "Carlos"
console.log(empleado.departamento) // "Ingenieria"
Extensión múltiple
Una interfaz puede extender varias interfaces a la vez, combinando todas sus propiedades:
interface ConNombre {
nombre: string
}
interface ConTimestamp {
creadoEn: Date
actualizadoEn: Date
}
interface ConEstado {
activo: boolean
}
interface Recurso extends ConNombre, ConTimestamp, ConEstado {
tipo: string
url: string
}
const recurso: Recurso = {
nombre: "Manual de TypeScript",
creadoEn: new Date(),
actualizadoEn: new Date(),
activo: true,
tipo: "documento",
url: "/docs/typescript"
}
console.log(recurso.nombre) // "Manual de TypeScript"
console.log(recurso.activo) // true
Convertir propiedades opcionales en obligatorias
Al extender una interfaz, es posible redefinir propiedades opcionales como obligatorias, siempre que el tipo sea compatible:
interface ConfigBase {
host: string
puerto?: number
reintentos?: number
}
interface ConfigProduccion extends ConfigBase {
puerto: number
reintentos: number
nivelLog: "info" | "warn" | "error"
}
const devConfig: ConfigBase = {
host: "localhost"
}
const prodConfig: ConfigProduccion = {
host: "api.producción.com",
puerto: 443,
reintentos: 3,
nivelLog: "error"
}
console.log(devConfig.host) // "localhost"
console.log(prodConfig.reintentos) // 3
Declaration merging
Una de las caracteristicas exclusivas de las interfaces frente a los alias de tipo es la declaration merging (fusión de declaraciones). Cuando se declaran dos interfaces con el mismo nombre en el mismo ámbito, TypeScript las fusiona automáticamente en una sola interfaz que contiene los miembros de ambas:
interface Ventana {
ancho: number
alto: number
}
interface Ventana {
titulo: string
}
// La interfaz Ventana ahora tiene ancho, alto y titulo
const ventana: Ventana = {
ancho: 800,
alto: 600,
titulo: "Mi aplicación"
}
console.log(ventana.titulo) // "Mi aplicación"
console.log(ventana.ancho) // 800
Las propiedades no funcionales de ambas declaraciones deben tener tipos compatibles. Si dos declaraciones definen la misma propiedad con tipos diferentes, TypeScript genera un error.
Uso práctico: ampliar tipos de terceros
Declaration merging es especialmente útil para ampliar tipos de bibliotecas externas sin modificar su código fuente:
interface Peticion {
url: string
método: string
}
// En otro lugar del código, ampliamos la interfaz
interface Peticion {
cabeceras?: Record<string, string>
cuerpo?: unknown
}
const peticion: Peticion = {
url: "/api/usuarios",
método: "POST",
cabeceras: { "Content-Type": "application/json" },
cuerpo: { nombre: "Ana" }
}
console.log(peticion.url) // "/api/usuarios"
console.log(peticion.método) // "POST"
Merging con métodos sobrecargados
Cuando ambas declaraciones incluyen métodos con el mismo nombre pero firmas diferentes, se fusionan como sobrecargas:
interface Buscador {
buscar(termino: string): string[]
}
interface Buscador {
buscar(termino: string, limite: number): string[]
}
// Buscador ahora tiene dos firmas de buscar
const buscador: Buscador = {
buscar(termino: string, limite?: number): string[] {
const resultados = ["resultado1", "resultado2", "resultado3"]
if (limite !== undefined) {
return resultados.slice(0, limite)
}
return resultados
}
}
console.log(buscador.buscar("test")) // ["resultado1", "resultado2", "resultado3"]
console.log(buscador.buscar("test", 2)) // ["resultado1", "resultado2"]
Interfaces para tipos de función y tipos híbridos
Interfaces como tipos de función
Una interfaz puede describir un tipo de función mediante una call signature sin nombre de método:
interface Comparador {
(a: string, b: string): number
}
const compararAlfabetico: Comparador = (a, b) => {
return a.localeCompare(b)
}
const compararPorLongitud: Comparador = (a, b) => {
return a.length - b.length
}
const palabras = ["typescript", "go", "python", "c"]
console.log([...palabras].sort(compararAlfabetico))
// ["c", "go", "python", "typescript"]
console.log([...palabras].sort(compararPorLongitud))
// ["c", "go", "python", "typescript"]
Los nombres de los parámetros en la interfaz no necesitan coincidir con los de la implementación. TypeScript verifica los tipos posicionalmente:
interface Predicado {
(valor: string): boolean
}
const esVacio: Predicado = (texto) => texto.length === 0
const contieneArroba: Predicado = (entrada) => entrada.includes("@")
console.log(esVacio("")) // true
console.log(contieneArroba("a@b")) // true
Tipos híbridos
Un tipo híbrido es una interfaz que combina una call signature con propiedades. Esto modela objetos en JavaScript que son invocables y además tienen estado:
interface Contador {
(): number
valor: number
reiniciar(): void
}
function crearContador(inicial: number = 0): Contador {
let cuenta = inicial
const contador = function(): number {
cuenta++
contador.valor = cuenta
return cuenta
} as Contador
contador.valor = cuenta
contador.reiniciar = () => {
cuenta = inicial
contador.valor = cuenta
}
return contador
}
const miContador = crearContador(0)
console.log(miContador()) // 1
console.log(miContador()) // 2
console.log(miContador.valor) // 2
miContador.reiniciar()
console.log(miContador.valor) // 0
console.log(miContador()) // 1
Interfaces con index signatures
Las interfaces pueden incluir index signatures para describir objetos con claves dinámicas:
interface Diccionario {
[clave: string]: string
}
const traducciones: Diccionario = {
hello: "hola",
world: "mundo",
goodbye: "adios"
}
traducciones["thanks"] = "gracias"
console.log(traducciones["hello"]) // "hola"
console.log(traducciones["thanks"]) // "gracias"
Las index signatures pueden combinarse con propiedades fijas, siempre que los tipos sean compatibles:
interface CabecerasHTTP {
"content-type": string
"authorization"?: string
[clave: string]: string | undefined
}
const cabeceras: CabecerasHTTP = {
"content-type": "application/json",
"authorization": "Bearer token123",
"x-custom-header": "valor-personalizado"
}
console.log(cabeceras["content-type"]) // "application/json"
console.log(cabeceras["x-custom-header"]) // "valor-personalizado"
Interfaces genéricas
Las interfaces admiten parámetros de tipo para crear estructuras reutilizables:
interface Respuesta<T> {
datos: T
exito: boolean
mensaje?: string
}
interface ListaPaginada<T> {
elementos: T[]
total: number
página: number
porPagina: number
}
const respuestaUsuario: Respuesta<{ nombre: string; edad: number }> = {
datos: { nombre: "Ana", edad: 30 },
exito: true
}
const listaProductos: ListaPaginada<{ nombre: string; precio: number }> = {
elementos: [
{ nombre: "Laptop", precio: 999 },
{ nombre: "Teclado", precio: 75 }
],
total: 50,
página: 1,
porPagina: 10
}
console.log(respuestaUsuario.datos.nombre) // "Ana"
console.log(listaProductos.elementos.length) // 2
console.log(listaProductos.total) // 50
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de TypeScript
Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Declarar interfaces con propiedades obligatorias, opcionales y readonly. Extender interfaces con extends para componer tipos. Aprovechar declaration merging para ampliar interfaces existentes. Crear interfaces para tipos de función y tipos híbridos.