Las interfaces en TypeScript funcionan como contratos que definen qué propiedades y tipos deben tener los objetos para ser considerados de un tipo específico. Cuando creamos una interfaz, estamos estableciendo una especificación con un nombre que describe qué campos y tipos debe contener un objeto para cumplir con ese contrato. Por ejemplo, podríamos definir una interfaz llamada UserData que exija que un objeto tenga las propiedades username de tipo string, createdAt de tipo Date y superuser de tipo boolean.
Aunque en capítulos anteriores hemos trabajado con type alias para crear tipos con nombre que representan estructuras, las interfaces tienen un enfoque particular. Históricamente, las interfaces ofrecían funcionalidades que los type alias no tenían, pero con el tiempo, TypeScript ha ido incorporando muchas de esas características a los type alias, reduciendo las diferencias entre ambos. Sin embargo, las interfaces siguen siendo muy utilizadas, especialmente cuando trabajamos con módulos de terceros que fueron escritos hace tiempo y que emplean interfaces. Además, las interfaces permiten crear tipos más versátiles y avanzados en ciertos contextos, algo que no siempre es posible con type alias.
Para ilustrar cómo se define una interfaz, podemos imaginar una función login que devuelve un objeto con información de usuario. Este objeto podría tener la siguiente forma:
interface UserData {
username: string;
createdAt: Date;
superuser: boolean;
}
function login(): UserData {
return {
username: "admin",
createdAt: new Date(),
superuser: true,
};
}
const data = login();
Aquí, la función login está tipada para devolver un objeto que cumple con la interfaz UserData. Esto significa que cualquier variable que reciba ese objeto, como data, será reconocida por TypeScript como un objeto que tiene exactamente las propiedades y tipos definidos en UserData.
Un aspecto importante de las interfaces es que cuando declaramos que un objeto es de un tipo interfaz, debemos implementar exactamente lo que la interfaz requiere. No podemos añadir propiedades adicionales que no estén definidas en la interfaz. Por ejemplo, si intentamos agregar un campo country al objeto data que no está declarado en UserData, TypeScript nos mostrará un error porque esa propiedad no forma parte del contrato definido por la interfaz.
Esto puede parecer restrictivo, pero tiene sentido porque cuando usamos una interfaz para tipar un objeto, TypeScript solo reconoce las propiedades declaradas en esa interfaz. Así, si intentamos acceder a data.country, no será posible, ya que country no está definido en UserData. Además, aunque internamente el objeto pueda tener más propiedades, TypeScript solo permitirá trabajar con las que están especificadas en la interfaz.
En el contexto de la programación orientada a objetos, usamos el término implementar para referirnos a que un objeto o clase cumple con la especificación de una interfaz. Más adelante, cuando trabajemos con clases, veremos que implements es una palabra clave que nos permite asegurar que una clase cumple con una interfaz determinada, garantizando que tiene todas las propiedades y métodos que la interfaz define.
Por ahora, al trabajar con objetos simples, debemos recordar que las interfaces exigen que implementemos exactamente lo que se declara, ni más ni menos. Esto nos ayuda a mantener estructuras claras y seguras en nuestro código, evitando errores y facilitando la comprensión de qué datos se están manejando en cada momento.
En próximos capítulos profundizaremos en aspectos más avanzados de las interfaces y cómo aprovecharlas al máximo en TypeScript, especialmente en combinación con clases y otros patrones de programación.