Las interfaces en TypeScript no solo nos permiten definir atributos para nuestras estructuras, sino que también podemos declarar funciones dentro de ellas. Esto significa que cuando una estructura, ya sea un objeto o una clase, implementa una interfaz, debe incluir los métodos que describen el comportamiento que la interfaz exige. Sin embargo, la interfaz solo nos indica qué funciones deben existir y qué tipo de retorno tienen, pero no nos dice cómo implementarlas. Por ejemplo, si definimos una interfaz UserData con una función logout que no retorna nada (void), cualquier objeto que implemente esta interfaz deberá tener ese método, aunque la implementación concreta quede a nuestra elección.
Podemos declarar estas funciones dentro de la interfaz de forma sencilla, indicando el nombre y el tipo de retorno, como en:
interface UserData {
logout(): void;
rename(newUsername: string): void;
username: string;
}
Al implementar esta interfaz en un objeto, no es necesario volver a declarar los tipos de los parámetros o el tipo de retorno, ya que TypeScript los infiere automáticamente a partir de la interfaz. Por ejemplo:
const user: UserData = {
username: "admin",
logout() {
console.log("Adiós");
},
rename(newUsername) {
this.username = newUsername; // Aquí podríamos implementar la lógica para renombrar
}
};
Además de las funciones, las interfaces en TypeScript nos permiten usar modificadores para controlar el comportamiento de sus propiedades. Uno de los más importantes es readonly, que impide que una propiedad sea modificada una vez que la estructura ha sido creada e inicializada. Esto es útil para proteger atributos que no deberían cambiar directamente, forzando a que cualquier cambio se realice a través de métodos específicos, como la función rename en el ejemplo anterior.
Por ejemplo, si queremos que username sea inmutable desde fuera, podemos declararlo así:
interface UserData {
readonly username: string;
logout(): void;
rename(newUsername: string): void;
}
Con esto, si intentamos hacer algo como:
user.username = "nuevoNombre"; // Error: no se puede asignar a 'username' porque es readonly
TypeScript nos avisará de que no podemos modificar esa propiedad directamente. Eso sí, es importante entender que readonly solo protege la referencia a la propiedad, no el contenido interno si es un objeto. Por ejemplo, si tenemos una propiedad personal que es un objeto, y la declaramos como readonly, no podremos reasignar personal a otro objeto, pero sí podremos modificar las propiedades internas de ese objeto:
interface UserData {
readonly personal: {
name: string;
mail: string;
};
}
const user: UserData = {
personal: {
name: "Admin",
mail: "admin@example.com"
}
};
user.personal = { name: "Otro", mail: "otro@example.com" }; // Error: no se puede reasignar 'personal'
user.personal.name = "Nuevo Nombre"; // Esto sí es permitido
Por otro lado, las interfaces también nos permiten definir propiedades opcionales, que pueden o no estar presentes en la estructura. Esto se hace añadiendo un signo de interrogación ? junto al nombre de la propiedad. Por ejemplo:
interface UserData {
username: string;
createdDate?: Date;
}
Aquí, createdDate puede estar definido o no. Esto implica que cuando accedemos a esta propiedad, debemos tener cuidado porque su valor puede ser undefined. TypeScript nos obliga a manejar esta posibilidad para evitar errores en tiempo de ejecución. Por ejemplo, si queremos usar un método de createdDate, como toISOString(), debemos asegurarnos de que la propiedad está definida:
if (user.createdDate) {
console.log(user.createdDate.toISOString());
}
Dentro del bloque if, TypeScript entiende que createdDate no es undefined, por lo que podemos usar sus métodos sin problemas. Fuera de ese bloque, el compilador nos advertirá que la propiedad puede ser undefined.
Para facilitar el acceso seguro a propiedades opcionales, TypeScript ofrece un operador especial llamado encadenamiento opcional ?.. Con él, podemos llamar a métodos o acceder a propiedades sin preocuparnos de que el valor sea undefined, ya que si lo es, la expresión simplemente devolverá undefined sin lanzar un error:
console.log(user.createdDate?.toISOString());
Finalmente, si estamos absolutamente seguros de que una propiedad opcional está definida en un momento dado, podemos usar el operador de aserción no nula ! para indicarle a TypeScript que ignore la posibilidad de undefined. Sin embargo, esto debe usarse con precaución, porque si la propiedad realmente no está definida, se producirá un error en tiempo de ejecución:
console.log(user.createdDate!.toISOString());
En resumen, las interfaces en TypeScript nos ofrecen una forma poderosa de definir tanto atributos como funciones que deben implementar nuestras estructuras, y con modificadores como readonly y propiedades opcionales podemos controlar la mutabilidad y la presencia de datos, mejorando la seguridad y robustez de nuestro código.