En typescript hay dos maneras principales para declarar la forma de un objeto: interfaces y alias de tipos (interfaces & type aliases). Podemos encontrar mucha información al respecto como el vídeo de domini code. No obstante vamos a explicar las características de las dos formas:

Objetos/Funciones

Ambas son muy parecidas y para la mayoría de los casos funcionan igual diferenciándose en su sintaxis.

Sintaxis para declarar interfaces:

interface Persona {
  nombre: string;
  edad: number;
}

interface SetPersona {
  (nombre: string, edad: number): void;
}

Sintaxis para declarar tipos:

type Persona = {
  nombre: string;
  edad: number;
}

type SetPersona = (nombre: string, edad: number) => void;

Ambas se construyen de igual forma:

const persona: Persona = {
  nombre: ‘Mike’,
  edad: 30
}

const setPersona: SetPersona = 
  (nombre: string, edad: number): void => {
    console.log(nombre, edad);
  }

Otros “Tipos”

A diferencia de la interfaz, el alias de tipo también se puede utilizar para otros tipos, como primitivas, uniones y tuplas.

Como primitivo:

type Guid = string;

Para uniones:

type PersonaNombre = { nombre: string; };
type PersonaEdad = { edad: number; };

type Persona = PersonaNombre | PersonaEdad;

Para tuplas:

type Persona = [string, number];

Extender

Ambos se pueden extender, pero nuevamente, la sintaxis cambia. Observa que ambas no son excluyentes entre sí, una interfaz puede extender un alias de tipo y viceversa.

Interfaz extiende interfaz:

interface PersonaNombre {
  nombre: string;
}

interface Persona extends PersonaNombre {
  edad: number;
}

Alias de tipo extiende alias de tipo:

type PersonaNombre = {
  nombre: string;
}

type Persona = PersonaNombre & {
  edad: number;
}

Interfaz extiende alias de tipo:

type PersonaNombre = {
  nombre: string;
}

interface Persona extends PersonaNombre {
  edad: number;
}

Alias de tipo extiende interfaz:

interface PersonaNombre {
  nombre: string;
}

type Persona = PersonaNombre & {
  edad: number;
}

Implementación de una clase

Una clase puede implementar una interfaz o un alias de tipo, ambos exactamente de la misma manera. Sin embargo no se puede implementar/extender a un alias de tipo que esté definido como una unión.

Immplementación interfaz:

interface Persona { 
  nombre: string; 
  edad: number; 
}
class PersonaImpl implements Persona { 
  nombre = ‘Mike’; 
  edad = 30; 
} 

Implementación tipo:

type Persona = {
  nombre: string; 
  edad: number; 
};
class PersonaImpl implements Persona {
  nombre = ‘Mike’; 
  edad = 30; 
}

Error, no se puede implementar:

type PersonaUnion = { nombre: string; } | { edad: number; }; 

//ERROR: no se puede implementar 
class PersonaUnionImpl implements PersonaUnion { 
  nombre = ‘Mike’; 
  edad = 30; 
}

Declaration merging

Al contrario que un alias de tipo, una interfaz la podemos definir múltiples veces y será tratada como una interfaz única (con los miembros de todas las declaraciones fusionados).

interface Persona {
  nombre: string;
}
interface Persona {
  edad: number; 
}

const persona: Persona = {
  nombre: ‘Mike’,
  edad: 30;
}

¿Cuándo utilizar cada una?

Podemos ver que en la documentación oficial de typescriptlang.org, no solo nos indica las diferencias si no que también nos comenta que en muchos casos podemos usar cualquiera de las dos maneras libremente. Esto significa dependerá mucho de las preferencias de quien desarrolle el código (o especificaciones del proyecto) siempre teniendo en cuenta las características y restricciones de cada una. No obstante hay que ser consistentes, si se utiliza una forma hay que mantenerla al menos en ese proyecto.

Aún así, se puede usar la interfaz cuando:

  1. Se trabaje con programación orientada a objetos: Si se define una clase y tiene que implementarse pues que implemente una interfaz.
  2. Si se necesita declarar la forma de un objeto.
  3. Para las migraciones se puede usar el “declaration merging” pero que al final de la migración derivará en la definición de una única interfaz.

Las únicas veces que se puede usar un alias de tipo sería cuando se necesita para crear un tipo, uniones y tuplas.

Además, ninguna de las dos va a generar código javascript en las declaraciones cuando vayamos a transpilar el código , esto es, que no aumentará el tamaño del bundle final.

Para finalizar te diré que mi preferencia personal es utilizar interfaces (se ha notado?) y que estas empiecen por «I» (IPersona), pero en el post no lo he puesto para que tenga el mismo nombre la interfaz que el tipo.