TypeScript: Objetos y tipos personalizados

TypeScript: Objetos y tipos personalizados

Tabla de Contenido

Note

Tercer artículo de la serie sobre TypeScript. Diseñaremos la forma de nuestros objetos con tipos personalizados, definiremos campos opcionales y métodos, y permitiremos que una variable acepte múltiples tipos.

Modelar correctamente la estructura de los objetos es donde TypeScript empieza a pagar dividendos en proyectos reales. Un tipo personalizado bien diseñado centraliza la definición de una entidad, propaga su forma por todo el código y convierte cualquier desviación en un error de compilación. Analicemos cómo pasar de objetos literales sueltos a un modelo de tipos mantenible.

Objetos literales y la inferencia estructural

Los objetos literales funcionan igual que en JavaScript, pero TypeScript infiere automáticamente su estructura. Esa inferencia es justamente lo que impide reasignar un objeto con una forma distinta:

let flash = {
  name: 'Barry Allen',
  age: 24,
  powers: ['super velocidad', 'viajar en el tiempo']
}

flash = { name2: 'Otro nombre' }
// Error: el literal solo puede especificar propiedades conocidas;
// 'name2' no existe en el tipo inferido y faltan 'name', 'age', 'powers'.

TypeScript bloquea la reasignación porque el nuevo literal no respeta la forma deducida de flash. Este comportamiento evita introducir campos accidentales o incompletos.

Tipar objetos de forma inline

Podemos declarar explícitamente la forma del objeto usando la misma sintaxis de anotación que venimos aplicando, incluyendo campos opcionales con ?:

let flash: { name: string, age?: number, powers: string[] } = {
  name: 'Barry Allen',
  age: 24,
  powers: ['super velocidad', 'viajar en el tiempo']
}

flash = {
  name: 'Clark Kent',
  powers: ['volar', 'rayo láser']   // 'age' es opcional, puede omitirse
}

Info

La posición de un campo opcional dentro de la anotación es irrelevante. age? puede ir en medio de la definición sin afectar la validez de los objetos que lo omitan.

El problema de este enfoque inline es la duplicación: si dos variables deben compartir la misma forma, hay que repetir la anotación completa en cada una y mantenerlas sincronizadas manualmente. Esto no escala.

Tipos personalizados con type

La solución es definir la estructura una sola vez con la palabra clave type y reutilizarla en todo el código. Esto centraliza el modelo en un único punto de verdad:

type Hero = {
  name: string;
  age?: number;
  powers: string[];
  getName?: () => string;
}

let flash: Hero = {
  name: 'Barry Allen',
  age: 24,
  powers: ['super velocidad', 'viajar en el tiempo']
}

let superman: Hero = {
  name: 'Clark Kent',
  age: 30,
  powers: ['volar', 'rayo láser'],
  getName() {
    return this.name;
  }
}

El tipo Hero declara name y powers como obligatorios, age como opcional y getName como un método opcional cuya firma es () => string. Cualquier objeto anotado con Hero debe respetar este contrato, y un solo cambio en la definición se propaga a todas las variables que lo usan.

flowchart TD
    T[type Hero] --> A[flash: Hero]
    T --> B[superman: Hero]
    T --> C[...otros héroes]
    note["Un cambio en Hero
se propaga a todos"] -.-> T

Tip

Define un type para toda entidad que aparezca más de una vez. La regla práctica: si copiarías la anotación inline por segunda vez, conviértela en un tipo personalizado.

Permitir múltiples tipos con uniones

Una variable puede aceptar más de un tipo mediante el operador de unión |. Esto resulta útil para valores que legítimamente cambian de forma según el contexto:

type Hero = {
  name: string;
  age?: number;
  powers: string[];
  getName?: () => string;
}

let myCustomVariable: Hero | string | number = '¡Hola mundo!'

myCustomVariable = 42          // válido: number
myCustomVariable = {           // válido: Hero
  name: 'Diana',
  powers: ['fuerza sobrehumana']
}

myCustomVariable puede contener un Hero, un string o un number. El typechecker permitirá únicamente operaciones válidas para todos los miembros de la unión hasta que acotes el tipo con un type guard (por ejemplo, typeof o una comprobación de propiedad).

Conclusión

Pasar de objetos literales inline a tipos personalizados con type es el salto que transforma TypeScript de una simple validación a una herramienta de diseño. Un tipo centraliza la forma de una entidad, los campos opcionales modelan la realidad de los datos incompletos, los métodos definen comportamiento esperado y las uniones aportan flexibilidad controlada.

En el próximo artículo de la serie abordaremos las funciones: anotación de parámetros, tipos de retorno, parámetros opcionales y sobrecargas.

Etiquetas :