Classes - Class en TypeScript
En TypeScript, una clase es una plantilla para crear un objeto. Una clase encapsula datos para el objeto y métodos para manipular los datos. En TypeScript, una clase se define utilizando la palabra clave class
.
Recursos
- doc ts oficial
- POO en vanilla Javascript (sin TS) 👇🏽👇🏽👇🏽
Sintaxis de una clase
- Declara una clase con la palabra clave
class
. - El nombre de la clase debe comenzar con una letra mayúscula (por convención).
- El cuerpo de la clase está entre llaves
{}
. - Las propiedades y métodos de la clase se definen dentro del cuerpo de la clase.
- En este ejemplo,
name
yage
son propiedades públicas de la clasePet
.
class Pet {
name: string;
age: number;
}
const myPet = new Pet();
myPet.name = "Tommy";
myPet.age = 5;
console.log(myPet.name); // Tommy
Property 'name' has no initializer and is not definitely assigned in the constructor.
Este error puede ocurrir con TypeScript 2.7 en modo "estricto". TypeScript 2.7 introdujo un nuevo indicador llamado --strictPropertyInitialization
, que le indica al compilador que verifique que cada propiedad de instancia de una clase se inicialice en el cuerpo del constructor o mediante un inicializador de propiedad.
assertion operator !
En términos simples, el operador de aserción (!) en TypeScript le dice al compilador que una propiedad o variable no es null
ni undefined
. Esto se usa para evitar errores de tipo en tiempo de compilación cuando se está seguro de que una propiedad existe, aunque TypeScript no pueda inferirlo automáticamente.
class Pet {
name!: string;
age!: number;
}
Al usar este operador, le estás diciendo al compilador que confíe en ti sobre la existencia de una propiedad o variable, lo cual puede ser peligroso si no estás completamente seguro de que no es null ni undefined.
let myValue: string | undefined;
// Si myValue es undefined, esto causará un error en tiempo de ejecución
console.log(myValue!.toUpperCase());
En este caso, si myValue es undefined
, intentar llamar a toUpperCase()
resultará en un error en tiempo de ejecución.
Puedes usar el operador de coalescencia nula (??):
let myValue: string | undefined = undefined;
console.log((myValue ?? "Valor predeterminado").toUpperCase());
Propiedades de una clase
Las propiedades de una clase son variables que se declaran dentro de una clase. Las propiedades de una clase pueden ser públicas (public), privadas (private), protegidas (protected) o de solo lectura (readonly).
Ejemplo:
class Pet {
// Propiedad pública
name: string;
// Propiedad privada
private age: number;
// Propiedad protegida
protected breed: string;
// Propiedad de solo lectura
readonly color: string = "Brown";
}
Private vs Protected
- Private: Las propiedades privadas solo se pueden acceder dentro de la clase en la que se declaran.
- Protected: Las propiedades protegidas se pueden acceder dentro de la clase en la que se declaran y en las clases derivadas.
Esto lo veremos más adelante en la sección de herencia.
readonly
En TypeScript, puedes declarar una propiedad de solo lectura utilizando la palabra clave readonly
. Una propiedad de solo lectura solo se puede asignar un valor cuando se inicializa o en el constructor de la clase. No en ambos.
class Pet {
readonly name: string = "Tommy";
}
const myPet = new Pet("Tommy");
// Esto dará un error en tiempo de compilación
myPet.name = "Buddy";
Constructores
Un constructor es un método especial que se llama automáticamente cuando se crea un objeto de una clase. En TypeScript, un constructor se define utilizando la palabra clave constructor
.
Ejemplo:
class Pet {
public name: string;
private age: number;
protected breed = "Unknown";
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public getDescription(): string {
return `${this.name} is ${this.age} years old.`;
}
}
const myPet = new Pet("Buddy", 3);
console.log(myPet.getDescription()); // Output: Buddy is 3 years old.
En términos muy sencillos, this
en TypeScript (y en otros lenguajes orientados a objetos) se refiere al objeto actual en el que se está ejecutando el código. Es una forma de acceder a las propiedades y métodos del objeto desde dentro de sus propios métodos.
En tu método getDescription
, this
se refiere a la instancia actual de la clase Pet. Así es como puedes acceder a las propiedades name y age de esa instancia.
Es obligatorio utilizar this en TypeScript (y en otros lenguajes orientados a objetos) para referirse a las propiedades y métodos del objeto actual porque:
- Claridad y Desambiguación: this deja claro que estás accediendo a una propiedad o método del objeto actual. Sin this, el compilador no sabría si te refieres a una variable local, un parámetro del método o una propiedad del objeto.
- Contexto del Objeto: this proporciona el contexto del objeto en el que se está ejecutando el código. Esto es especialmente importante en métodos de instancia donde necesitas acceder a las propiedades del objeto.
Constructor abreviado
En TypeScript, puedes abreviar la declaración de propiedades y el constructor utilizando el modificador de acceso en el constructor.
Ejemplo:
class Pet {
constructor(
public name: string,
private age: number,
protected breed = "Unknown"
) {}
public getDescription(): string {
return `${this.name} - ${this.breed} is ${this.age} years old.`;
}
}
const myPet = new Pet("Buddy", 3, "Golden Retriever");
console.log(myPet.getDescription()); // Output: Buddy - Golden Retriever is 3 years old.
En este ejemplo, las propiedades name
, age
y breed
se declaran en el constructor. Esto es una forma abreviada de declarar propiedades y asignarles valores en el constructor.
Herencia
La herencia es un concepto en el que una clase (subclase) hereda propiedades y métodos de otra clase (superclase). En TypeScript, puedes lograr la herencia utilizando la palabra clave extends
.
Ejemplo:
class Pet {
constructor(
public name: string,
private age: number,
readonly color: string,
protected breed = "Unknown"
) {}
public getDescription(): string {
return `${this.name} - ${this.breed} is ${this.age} years old and has ${this.color} fur.`;
}
}
class Dog extends Pet {
constructor(name: string, age: number, color: string, breed: string) {
// super se usa para llamar al constructor de la clase padre
// se debe respetar la firma del constructor de la clase padre
super(name, age, color, breed);
}
public bark(): void {
console.log("Woof! Woof!");
}
}
class Cat extends Pet {
constructor(name: string, age: number, color: string) {
super(name, age, color);
}
public meow(): void {
console.log("Meow! Meow!");
}
}
const dog = new Dog("Buddy", 5, "Golden", "Golden Retriever");
console.log(dog);
dog.bark();
const cat = new Cat("Whiskers", 3, "White");
console.log(cat);
cat.meow();
En este ejemplo, la clase Dog
y la clase Cat
heredan de la clase Pet
. La clase Dog
hereda las propiedades y métodos de la clase Pet
y también define un método adicional bark
. La clase Cat
hereda las propiedades y métodos de la clase Pet
y también define un método adicional meow
.
Polimorfismo y Private Properties
class Dog extends Pet {
constructor(name: string, age: number, color: string, breed: string) {
super(name, age, color, breed);
}
public bark(): void {
console.log("Woof! Woof!");
}
// Sobrescribe el método getDescription de la clase Pet
// Esto se conoce como "polimorfismo"
public getDescription(): string {
// super tambien se puede usar para acceder a los métodos de la clase padre
return `${super.getDescription()} It has a ${this.breed} breed.`;
}
// Aquí se genera un error porque la propiedad age es privada en la clase Pet
// private solo se puede acceder dentro de la clase en la que se declara
public getAge(): number {
return this.age;
}
}
Getter y Setter
En TypeScript, puedes definir métodos especiales llamados getters
y setters
(también posible en JS) para acceder y modificar propiedades de una clase.
class Cat extends Pet {
constructor(
name: string,
age: number,
color: string,
readonly lifes: number
) {
super(name, age, color);
}
public meow(): void {
console.log("Meow! Meow!");
}
// Getter: Obtiene el valor de la propiedad lifes
// Se puede acceder a la propiedad lifes como si fuera una propiedad
// Cómo regla no puede recibir parámetros
get getLifes(): number {
return this.lifes;
}
// Setter: Establece el valor de la propiedad lifes
// Se puede establecer la propiedad lifes como si fuera una propiedad
// Cómo regla solo puede recibir un parámetro
set setLifes(lifes: number) {
this.lifes = lifes;
}
}
const cat = new Cat("Whiskers", 3, "White", 7);
// Esto nos da la posibilidad de cambiar el valor de la propiedad lifes a través del setter
cat.setLifes = 8;
console.log(cat);
// Esto nos da la posibilidad de obtener el valor de la propiedad lifes a través del getter
console.log(cat.getLifes);
Clases Abstractas
En TypeScript, una clase abstracta es una clase que no se puede instanciar directamente. Se utiliza como plantilla para otras clases que extienden la clase abstracta.
abstract class Pet {
constructor(
public name: string,
private age: number,
readonly color: string,
protected breed = "Unknown"
) {}
public getDescription(): string {
return `${this.name} - ${this.breed} is ${this.age} years old and has ${this.color} fur.`;
}
}
En este ejemplo, la clase Pet
es una clase abstracta. No se puede instanciar directamente, pero se puede utilizar como plantilla para otras clases que extienden la clase Pet
.
Métodos Estáticos
En TypeScript, un método estático es un método que se puede llamar en la clase misma, en lugar de en una instancia de la clase. Se definen utilizando la palabra clave static
.
class Pet {
constructor(
public name: string,
private age: number,
readonly color: string,
protected breed = "Unknown"
) {}
public getDescription(): string {
return `${this.name} - ${this.breed} is ${this.age} years old and has ${this.color} fur.`;
}
static descriptionClass(): string {
return "This class is used to create pets.";
}
}
console.log(Pet.descriptionClass());
Implements
En TypeScript, las interfaces se utilizan para definir la forma de un objeto, especificando qué propiedades y métodos debe tener.
Solo está obligada a cumplir con el contrato mínimo definido por la interfaz. Esto significa que la clase debe tener al menos las propiedades y métodos especificados en la interfaz, pero puede tener propiedades y métodos adicionales.
Esto es útil para garantizar que las clases sigan una estructura específica y para habilitar el polimorfismo.
interface Animal {
name: string;
age: number;
getDescription(): string;
}
class Dog implements Animal {
constructor(public name: string, public age: number, public breed: string) {}
public getDescription(): string {
return `${this.name} is a Dog and is ${this.age} years old.`;
}
public bark(): void {
console.log("Woof! Woof!");
}
}
const dog = new Dog("Buddy", 5, "Golden Retriever");
Class vs Interface
Estas son algunas diferencias clave entre una interfaz y una clase en TypeScript:
Interfaces
- Definición de Contrato: Una interfaz define un contrato que las clases pueden implementar. Especifica qué propiedades y métodos deben estar presentes, pero no proporciona implementaciones.
- Sin Implementación: Las interfaces no pueden contener implementaciones de métodos ni inicializaciones de propiedades. Solo definen la estructura.
Clases
- Definición y Implementación: Una clase define tanto la estructura (propiedades y métodos) como las implementaciones de esos métodos.
- Inicialización de Propiedades: Las clases pueden inicializar valores por defecto para sus propiedades. Herencia: Las clases pueden extender (heredar de) una sola clase base, pero pueden implementar múltiples interfaces.
- Instanciación: Las clases pueden ser instanciadas para crear objetos.
Resumen
En TypeScript, una clase es una plantilla para crear un objeto. Una clase encapsula datos para el objeto y métodos para manipular los datos. En TypeScript, una clase se define utilizando la palabra clave class
.
- Sintaxis de una clase: Una clase se declara con la palabra clave
class
. El cuerpo de la clase está entre llaves{}
. Las propiedades y métodos de la clase se definen dentro del cuerpo de la clase. - Propiedades de una clase: Las propiedades de una clase son variables que se declaran dentro de una clase. Las propiedades de una clase pueden ser públicas (public), privadas (private), protegidas (protected) o de solo lectura (readonly).
- Constructores: Un constructor es un método especial que se llama automáticamente cuando se crea un objeto de una clase. En TypeScript, un constructor se define utilizando la palabra clave
constructor
. - Herencia: La herencia es un concepto en el que una clase (subclase) hereda propiedades y métodos de otra clase (superclase). En TypeScript, puedes lograr la herencia utilizando la palabra clave
extends
. - Polimorfismo: El polimorfismo es la capacidad de una clase para proporcionar una implementación específica de un método que ya está definido en una de sus superclases. En TypeScript, puedes lograr el polimorfismo sobrescribiendo métodos de la clase padre en la clase hija.
- Getter y Setter: En TypeScript, puedes definir métodos especiales llamados
getters
ysetters
para acceder y modificar propiedades de una clase. - Clases Abstractas: En TypeScript, una clase abstracta es una clase que no se puede instanciar directamente. Se utiliza como plantilla para otras clases que extienden la clase abstracta.
- Métodos Estáticos: En TypeScript, un método estático es un método que se puede llamar en la clase misma, en lugar de en una instancia de la clase. Se definen utilizando la palabra clave
static
. - Implements: En TypeScript, las interfaces se utilizan para definir la forma de un objeto, especificando qué propiedades y métodos debe tener. Una clase puede implementar una o más interfaces.