Classes
Sintaxe Comum de Classes
A palavra-chave class é usada no TypeScript para definir uma classe. Abaixo, você pode ver um exemplo:
class Person { private name: string; private age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } public sayHi(): void { console.log( `Olá, meu nome é ${this.name} e eu tenho ${this.age} anos.` ); }}A palavra-chave class é usada para definir uma classe chamada “Person”.
A classe possui duas propriedades privadas: name do tipo string e age do tipo number.
O construtor é definido usando a palavra-chave constructor. Ele recebe name e age como parâmetros e os atribui às propriedades correspondentes.
A classe possui um método public chamado sayHi que registra uma mensagem de saudação.
Para criar uma instância de uma classe no TypeScript, você pode usar a palavra-chave new seguida pelo nome da classe, seguida de parênteses (). Por exemplo:
const myObject = new Person('John Doe', 25);myObject.sayHi(); // Saída: Olá, meu nome é John Doe e eu tenho 25 anos.Construtor
Os construtores são métodos especiais dentro de uma classe que são usados para inicializar as propriedades do objeto quando uma instância da classe é criada.
class Person { public name: string; public age: number;
constructor(name: string, age: number) { this.name = name; this.age = age; }
sayHello() { console.log( `Olá, meu nome é ${this.name} e eu tenho ${this.age} anos.` ); }}
const john = new Person('Simon', 17);john.sayHello();É possível sobrecarregar um construtor usando a seguinte sintaxe:
type Sex = 'm' | 'f';
class Person { name: string; age: number; sex: Sex;
constructor(name: string, age: number, sex?: Sex); constructor(name: string, age: number, sex: Sex) { this.name = name; this.age = age; this.sex = sex ?? 'm'; }}
const p1 = new Person('Simon', 17);const p2 = new Person('Alice', 22, 'f');No TypeScript, é possível definir múltiplas sobrecargas de construtor, mas você pode ter apenas uma implementação que deve ser compatível com todas as sobrecargas, o que pode ser alcançado usando um parâmetro opcional.
class Person { name: string; age: number;
constructor(); constructor(name: string); constructor(name: string, age: number); constructor(name?: string, age?: number) { this.name = name ?? 'Desconhecido'; this.age = age ?? 0; }
displayInfo() { console.log(`Nome: ${this.name}, Idade: ${this.age}`); }}
const person1 = new Person();person1.displayInfo(); // Nome: Desconhecido, Idade: 0
const person2 = new Person('John');person2.displayInfo(); // Nome: John, Idade: 0
const person3 = new Person('Jane', 25);person3.displayInfo(); // Nome: Jane, Idade: 25Construtores Privados e Protegidos
No TypeScript, os construtores podem ser marcados como privados ou protegidos, o que restringe sua acessibilidade e uso.
Construtores Privados (Private Constructors): Podem ser chamados apenas dentro da própria classe. Construtores privados são frequentemente usados em cenários onde você deseja impor um padrão singleton ou restringir a criação de instâncias a um método factual dentro da classe.
Construtores Protegidos (Protected Constructors): Construtores protegidos são úteis quando você deseja criar uma classe base que não deve ser instanciada diretamente, mas pode ser estendida por subclasses.
class BaseClass { protected constructor() {}}
class DerivedClass extends BaseClass { private value: number;
constructor(value: number) { super(); this.value = value; }}
// Tentar instanciar a classe base diretamente resultará em erro// const baseObj = new BaseClass(); // Erro: O construtor da classe 'BaseClass' é protegido.
// Criar uma instância da classe derivadaconst derivedObj = new DerivedClass(10);Modificadores de Acesso
Modificadores de Acesso private, protected e public são usados para controlar a visibilidade e acessibilidade dos membros da classe, como propriedades e métodos, em classes TypeScript. Esses modificadores são essenciais para impor o encapsulamento e estabelecer limites para acessar e modificar o estado interno de uma classe.
O modificador private restringe o acesso ao membro da classe apenas dentro da classe que o contém.
O modificador protected permite o acesso ao membro da classe dentro da classe que o contém e suas classes derivadas.
O modificador public fornece acesso irrestrito ao membro da classe, permitindo que ele seja acessado de qualquer lugar.
Get e Set
Getters e setters são métodos especiais que permitem definir comportamentos personalizados de acesso e modificação para propriedades de classe. Eles permitem encapsular o estado interno de um objeto e fornecer lógica adicional ao obter ou definir os valores das propriedades.
No TypeScript, os getters e setters são definidos usando as palavras-chave get e set, respectivamente. Aqui está um exemplo:
class MyClass { private _myProperty: string;
constructor(value: string) { this._myProperty = value; } get myProperty(): string { return this._myProperty; } set myProperty(value: string) { this._myProperty = value; }}Auto-acessores em Classes
O TypeScript versão 4.9 adiciona suporte para auto-acessores (auto-accessors), um recurso futuro do ECMAScript. Eles se assemelham a propriedades de classe, mas são declarados com a palavra-chave “accessor”.
class Animal { accessor name: string;
constructor(name: string) { this.name = name; }}Os auto-acessores são transformados em acessores get e set privados, operando em uma propriedade inacessível.
class Animal { #__name: string;
get name() { return this.#__name; } set name(value: string) { this.#__name = name; }
constructor(name: string) { this.name = name; }}this
No TypeScript, a palavra-chave this refere-se à instância atual de uma classe dentro de seus métodos ou construtores. Ela permite acessar e modificar as propriedades e métodos da classe de dentro de seu próprio escopo.
Fornece uma maneira de acessar e manipular o estado interno de um objeto dentro de seus próprios métodos.
class Person { private name: string; constructor(name: string) { this.name = name; } public introduce(): void { console.log(`Olá, meu nome é ${this.name}.`); }}
const person1 = new Person('Alice');person1.introduce(); // Olá, meu nome é Alice.Propriedades de Parâmetro
As propriedades de parâmetro permitem declarar e inicializar propriedades de classe diretamente dentro dos parâmetros do construtor, evitando o código repetitivo (boilerplate). Exemplo:
class Person { constructor( private name: string, public age: number ) { // As palavras-chave "private" e "public" no construtor // declaram e inicializam automaticamente as propriedades de classe correspondentes. } public introduce(): void { console.log( `Olá, meu nome é ${this.name} e eu tenho ${this.age} anos.` ); }}const person = new Person('Alice', 25);person.introduce();Classes Abstratas
Classes Abstratas são usadas no TypeScript principalmente para herança; elas fornecem uma maneira de definir propriedades e métodos comuns que podem ser herdados por subclasses. Isso é útil quando você deseja definir um comportamento comum e garantir que as subclasses implementem certos métodos. Elas fornecem uma maneira de criar uma hierarquia de classes onde a classe base abstrata fornece uma interface compartilhada e funcionalidade comum para as subclasses.
abstract class Animal { protected name: string;
constructor(name: string) { this.name = name; }
abstract makeSound(): void;}
class Cat extends Animal { makeSound(): void { console.log(`${this.name} mia (meows).`); }}
const cat = new Cat('Whiskers');cat.makeSound(); // Saída: Whiskers mia (meows).Com Genéricos
Classes com genéricos permitem definir classes reutilizáveis que podem trabalhar com diferentes tipos.
class Container<T> { private item: T;
constructor(item: T) { this.item = item; }
getItem(): T { return this.item; }
setItem(item: T): void { this.item = item; }}
const container1 = new Container<number>(42);console.log(container1.getItem()); // 42
const container2 = new Container<string>('Olá');container2.setItem('Mundo');console.log(container2.getItem()); // MundoDecoradores (Decorators)
Os decoradores fornecem um mecanismo para adicionar metadados, modificar comportamentos, validar ou estender a funcionalidade do elemento alvo. São funções que são executadas em tempo de execução. Múltiplos decoradores podem ser aplicados a uma declaração.
Os decoradores são recursos experimentais, e os exemplos a seguir são compatíveis apenas com a versão 5 do TypeScript ou superior usando ES6.
Para versões do TypeScript anteriores à 5, eles devem ser habilitados usando a propriedade experimentalDecorators em seu tsconfig.json ou usando --experimentalDecorators na sua linha de comando (mas o exemplo a seguir não funcionará).
Alguns dos casos de uso comuns para decoradores incluem:
- Observar mudanças de propriedade.
- Observar chamadas de métodos.
- Adicionar propriedades ou métodos extras.
- Validação em tempo de execução.
- Serialização e desserialização automática.
- Registro (Logging).
- Autorização e autenticação.
- Proteção contra erros (Error guarding).
Nota: Decoradores para a versão 5 não permitem decorar parâmetros.
Tipos de decoradores:
Decoradores de Classe (Class Decorators)
Os Decoradores de Classe são úteis para estender uma classe existente, como adicionar propriedades ou métodos, ou coletar instâncias de uma classe. No exemplo a seguir, adicionamos um método toString que converte a classe em uma representação de string.
type Constructor<T = {}> = new (...args: any[]) => T;
function toString<Class extends Constructor>( Value: Class, context: ClassDecoratorContext<Class>) { return class extends Value { constructor(...args: any[]) { super(...args); console.log(JSON.stringify(this)); console.log(JSON.stringify(context)); } };}
@toStringclass Person { name: string;
constructor(name: string) { this.name = name; }
greet() { return 'Hello, ' + this.name; }}const person = new Person('Simon');/* Registra:{"name":"Simon"}{"kind":"class","name":"Person"}*/Decorador de Propriedade (Property Decorator)
Os decoradores de propriedade são úteis para modificar o comportamento de uma propriedade, como alterar os valores de inicialização. No código a seguir, temos um script que define uma propriedade para estar sempre em letras maiúsculas:
function upperCase<T>( target: undefined, context: ClassFieldDecoratorContext<T, string>) { return function (this: T, value: string) { return value.toUpperCase(); };}
class MyClass { @upperCase prop1 = 'hello!';}
console.log(new MyClass().prop1); // Registra: HELLO!Decorador de Método (Method Decorator)
Os decoradores de método permitem alterar ou aprimorar o comportamento dos métodos. Abaixo está um exemplo de um registrador (logger) simples:
function log<This, Args extends any[], Return>( target: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext< This, (this: This, ...args: Args) => Return >) { const methodName = String(context.name);
function replacementMethod(this: This, ...args: Args): Return { console.log(`LOG: Entrando no método '${methodName}'.`); const result = target.call(this, ...args); console.log(`LOG: Saindo do método '${methodName}'.`); return result; }
return replacementMethod;}
class MyClass { @log sayHello() { console.log('Hello!'); }}
new MyClass().sayHello();Isso registra:
LOG: Entrando no método 'sayHello'.Hello!LOG: Saindo do método 'sayHello'.Decoradores de Getter e Setter
Decoradores de getter e setter permitem alterar ou aprimorar o comportamento dos acessores de classe. Eles são úteis, por exemplo, para validar atribuições de propriedades. Aqui está um exemplo simples de um decorador de getter:
function range<This, Return extends number>(min: number, max: number) { return function ( target: (this: This) => Return, context: ClassGetterDecoratorContext<This, Return> ) { return function (this: This): Return { const value = target.call(this); if (value < min || value > max) { throw 'Invalid'; } Object.defineProperty(this, context.name, { value, enumerable: true, }); return value; }; };}
class MyClass { private _value = 0;
constructor(value: number) { this._value = value; } @range(1, 100) get getValue(): number { return this._value; }}
const obj = new MyClass(10);console.log(obj.getValue); // Válido: 10
const obj2 = new MyClass(999);console.log(obj2.getValue); // Lança: Invalid!Metadados de Decorador (Decorator Metadata)
Os Metadados de Decorador simplificam o processo para que os decoradores apliquem e utilizem metadados em qualquer classe. Eles podem acessar uma nova propriedade de metadados no objeto de contexto, que pode servir como uma chave para primitivos e objetos.
As informações de metadados podem ser acessadas na classe via Symbol.metadata.
Os metadados podem ser usados para vários fins, como depuração, serialização ou injeção de dependência com decoradores.
//@ts-ignoreSymbol.metadata ??= Symbol('Symbol.metadata'); // Polify simples
type Context = | ClassFieldDecoratorContext | ClassAccessorDecoratorContext | ClassMethodDecoratorContext; // O contexto contém os metadados da propriedade: DecoratorMetadata
function setMetadata(_target: any, context: Context) { // Define o objeto de metadados com um valor primitivo context.metadata[context.name] = true;}
class MyClass { @setMetadata a = 123;
@setMetadata accessor b = 'b';
@setMetadata fn() {}}
const metadata = MyClass[Symbol.metadata]; // Obtém as informações de metadados
console.log(JSON.stringify(metadata)); // {"bar":true,"baz":true,"foo":true}Herança
Herança refere-se ao mecanismo pelo qual uma classe pode herdar propriedades e métodos de outra classe, conhecida como classe base ou superclasse. A classe derivada, também chamada de classe filha ou subclasse, pode estender e especializar a funcionalidade da classe base adicionando novas propriedades e métodos ou substituindo (overriding) os existentes.
class Animal { name: string;
constructor(name: string) { this.name = name; }
speak(): void { console.log('O animal faz um som'); }}
class Dog extends Animal { breed: string;
constructor(name: string, breed: string) { super(name); this.breed = breed; }
speak(): void { console.log('Woof! Woof!'); }}
// Cria uma instância da classe baseconst animal = new Animal('Animal Genérico');animal.speak(); // O animal faz um som
// Cria uma instância da classe derivadaconst dog = new Dog('Max', 'Labrador');dog.speak(); // Woof! Woof!"O TypeScript não suporta herança múltipla no sentido tradicional e, em vez disso, permite a herança de uma única classe base. O TypeScript suporta múltiplas interfaces. Uma interface pode definir um contrato para a estrutura de um objeto, e uma classe pode implementar múltiplas interfaces. Isso permite que uma classe herde comportamento e estrutura de múltiplas fontes.
interface Flyable { fly(): void;}
interface Swimmable { swim(): void;}
class FlyingFish implements Flyable, Swimmable { fly() { console.log('Voando...'); }
swim() { console.log('Nadando...'); }}
const flyingFish = new FlyingFish();flyingFish.fly();flyingFish.swim();A palavra-chave class no TypeScript, assim como no JavaScript, é frequentemente chamada de açúcar sintático (syntactic sugar). Ela foi introduzida no ECMAScript 2015 (ES6) para oferecer uma sintaxe mais familiar para criar e trabalhar com objetos de maneira baseada em classes. No entanto, é importante notar que o TypeScript, sendo um superconjunto do JavaScript, acaba sendo compilado para JavaScript, que permanece baseado em protótipos em seu núcleo.
Estáticos (Statics)
O TypeScript possui membros estáticos. Para acessar os membros estáticos de uma classe, você pode usar o nome da classe seguido por um ponto, sem a necessidade de criar um objeto.
class OfficeWorker { static memberCount: number = 0;
constructor(private name: string) { OfficeWorker.memberCount++; }}
const w1 = new OfficeWorker('James');const w2 = new OfficeWorker('Simon');const total = OfficeWorker.memberCount;console.log(total); // 2Inicialização de Propriedade
Existem várias maneiras de inicializar propriedades de uma classe no TypeScript:
Em linha (Inline):
No exemplo a seguir, esses valores iniciais serão usados quando uma instância da classe for criada.
class MyClass { property1: string = 'valor padrão'; property2: number = 42;}No construtor:
class MyClass { property1: string; property2: number;
constructor() { this.property1 = 'valor padrão'; this.property2 = 42; }}Usando parâmetros do construtor:
class MyClass { constructor( private property1: string = 'valor padrão', public property2: number = 42 ) { // Não há necessidade de atribuir os valores às propriedades explicitamente. } log() { console.log(this.property2); }}const x = new MyClass();x.log();Sobrecarga de Método
A sobrecarga de método permite que uma classe tenha múltiplos métodos com o mesmo nome, mas diferentes tipos de parâmetros ou um número diferente de parâmetros. Isso nos permite chamar um método de diferentes maneiras com base nos argumentos passados.
class MyClass { add(a: number, b: number): number; // Assinatura de sobrecarga 1 add(a: string, b: string): string; // Assinatura de sobrecarga 2
add(a: number | string, b: number | string): number | string { if (typeof a === 'number' && typeof b === 'number') { return a + b; } if (typeof a === 'string' && typeof b === 'string') { return a.concat(b); } throw new Error('Argumentos inválidos'); }}
const r = new MyClass();console.log(r.add(10, 5)); // Registra 15