Interfaces em TypeScript: Definição, Extensão e Merge Declaration na Prática Já leu

O que é uma Interface em TypeScript? Uma interface em TypeScript é um contrato que define a estrutura de um objeto. Ela descreve quais propriedades e métodos um objeto deve ter, sem implementação — apenas a assinatura. Pense em uma interface como um molde ou blueprint: você a define, e qualquer classe ou objeto que "implemente" essa interface deve seguir as regras estabelecidas. A grande vantagem é o type checking em tempo de desenvolvimento. O TypeScript verifica se você está usando o objeto corretamente antes mesmo de executar o código. Isso reduz bugs e torna seu código mais seguro e previsível. Diferente de classes, interfaces não existem em tempo de execução — o compilador as remove durante a transpilação para JavaScript. Definição Avançada e Propriedades Opcionais Interfaces podem incluir propriedades opcionais e de apenas leitura, proporcionando controle fino sobre a estrutura esperada. Uma propriedade opcional é marcada com , indicando que o objeto não precisa obrigatoriamente fornecê-la. Propriedades de apenas

O que é uma Interface em TypeScript?

Uma interface em TypeScript é um contrato que define a estrutura de um objeto. Ela descreve quais propriedades e métodos um objeto deve ter, sem implementação — apenas a assinatura. Pense em uma interface como um molde ou blueprint: você a define, e qualquer classe ou objeto que "implemente" essa interface deve seguir as regras estabelecidas.

A grande vantagem é o type checking em tempo de desenvolvimento. O TypeScript verifica se você está usando o objeto corretamente antes mesmo de executar o código. Isso reduz bugs e torna seu código mais seguro e previsível. Diferente de classes, interfaces não existem em tempo de execução — o compilador as remove durante a transpilação para JavaScript.

// Definindo uma interface simples
interface Usuario {
  id: number;
  nome: string;
  email: string;
  ativo: boolean;
}

// Criando um objeto que segue a interface
const usuario: Usuario = {
  id: 1,
  nome: "João Silva",
  email: "joao@example.com",
  ativo: true
};

// Se tentar atribuir um objeto incompleto, TypeScript gera erro
const usuarioIncompleto: Usuario = {
  id: 2,
  nome: "Maria"
  // ❌ Erro: propriedades 'email' e 'ativo' não foram atribuídas
};

Definição Avançada e Propriedades Opcionais

Interfaces podem incluir propriedades opcionais e de apenas leitura, proporcionando controle fino sobre a estrutura esperada. Uma propriedade opcional é marcada com ?, indicando que o objeto não precisa obrigatoriamente fornecê-la. Propriedades de apenas leitura usam readonly, garantindo que não serão modificadas após a criação.

Também é possível definir assinaturas de métodos dentro da interface, especificando qual o retorno esperado e quais parâmetros o método deve aceitar. Isso cria contratos mais robustos quando você precisa garantir comportamentos específicos em classes ou funções que implementam a interface.

interface Produto {
  readonly id: number;          // Não pode ser modificado
  nome: string;
  preco: number;
  descricao?: string;           // Opcional
  tags?: string[];              // Array opcional
  calcularDesconto(percentual: number): number;  // Método
}

class ProdutoFisico implements Produto {
  readonly id: number;
  nome: string;
  preco: number;
  descricao?: string;
  tags?: string[];

  constructor(id: number, nome: string, preco: number) {
    this.id = id;
    this.nome = nome;
    this.preco = preco;
  }

  calcularDesconto(percentual: number): number {
    return this.preco * (1 - percentual / 100);
  }
}

const produto = new ProdutoFisico(1, "Notebook", 3000);
produto.nome = "Notebook Gamer"; // ✅ Permitido
// produto.id = 2; // ❌ Erro: não pode atribuir a propriedade 'readonly'

console.log(produto.calcularDesconto(10)); // 2700

Extensão de Interfaces

A extensão de interfaces permite reutilizar e construir sobre interfaces já existentes, promovendo código DRY (Don't Repeat Yourself). Usando a palavra-chave extends, você cria uma nova interface que herda todas as propriedades e métodos de uma ou mais interfaces existentes, adicionando novas propriedades conforme necessário.

Uma interface pode estender múltiplas outras interfaces simultaneamente, criando estruturas complexas a partir de componentes menores e reutilizáveis. Isso é particularmente útil em aplicações grandes onde você tem conceitos base que são expandidos em diferentes contextos. A extensão também permite sobrescrever tipos: uma propriedade pode ser redefinida em uma interface derivada com um tipo mais específico.

// Interface base
interface Entidade {
  readonly id: number;
  dataCriacao: Date;
}

// Interface que estende Entidade
interface Usuario extends Entidade {
  nome: string;
  email: string;
  verificarEmail(): Promise<boolean>;
}

// Interface que estende múltiplas interfaces
interface AdminDeSistema extends Usuario {
  nivelAcesso: "admin" | "moderador" | "usuario";
  permissoes: string[];
  revogarPermissao(permissao: string): void;
}

// Implementação concreta
class Admin implements AdminDeSistema {
  readonly id: number;
  dataCriacao: Date;
  nome: string;
  email: string;
  nivelAcesso: "admin" | "moderador" | "usuario";
  permissoes: string[];

  constructor(
    id: number,
    nome: string,
    email: string,
    nivelAcesso: "admin" | "moderador" | "usuario"
  ) {
    this.id = id;
    this.dataCriacao = new Date();
    this.nome = nome;
    this.email = email;
    this.nivelAcesso = nivelAcesso;
    this.permissoes = [];
  }

  async verificarEmail(): Promise<boolean> {
    // Implementação da verificação
    return true;
  }

  revogarPermissao(permissao: string): void {
    this.permissoes = this.permissoes.filter(p => p !== permissao);
  }
}

const admin = new Admin(1, "Carlos", "carlos@admin.com", "admin");
admin.permissoes = ["criar_usuarios", "deletar_usuarios"];
admin.revogarPermissao("deletar_usuarios");
console.log(admin.permissoes); // ["criar_usuarios"]

Declaration Merging em Interfaces

Declaration merging (fusão de declarações) é um recurso exclusivo de TypeScript que permite declarar a mesma interface múltiplas vezes, e o compilador as combina automaticamente em uma única interface. Isso é especialmente útil quando você quer estender funcionalidades de tipos existentes ou quando trabalha com código que será integrado de múltiplas fontes.

Quando você declara a mesma interface duas vezes, as propriedades de ambas as declarações são combinadas. Isso funciona apenas com interfaces — classes e tipos não suportam merging. Um caso de uso prático é estender interfaces de bibliotecas externas sem modificar o código original, ou quando você tem múltiplos arquivos que constroem a mesma interface incrementalmente.

// Primeira declaração da interface
interface Configuracao {
  apiUrl: string;
  timeout: number;
}

// Segunda declaração — será mesclada automaticamente
interface Configuracao {
  debug: boolean;
  maxRetries: number;
}

// TypeScript trata como se fosse:
// interface Configuracao {
//   apiUrl: string;
//   timeout: number;
//   debug: boolean;
//   maxRetries: number;
// }

const config: Configuracao = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  debug: true,
  maxRetries: 3
};

console.log(config); // ✅ Todas as propriedades são obrigatórias

Merging com Namespaces

Declaration merging também funciona ao combinar interfaces com namespaces, permitindo organizar código em módulos lógicos. Essa técnica é comum quando você quer separar tipos relacionados em diferentes espaços de nome para melhor organização.

// Declarar um namespace com uma interface
namespace Configuracao {
  export interface Banco {
    host: string;
    porta: number;
    usuario: string;
    senha: string;
  }
}

// Declarar uma interface no escopo global com o mesmo nome
interface Configuracao {
  ambiente: "producao" | "desenvolvimento";
}

// Agora você acessa ambas:
const configBanco: Configuracao.Banco = {
  host: "localhost",
  porta: 5432,
  usuario: "admin",
  senha: "senha123"
};

const config: Configuracao = {
  ambiente: "desenvolvimento"
};

Estendendo Tipos Globais

Um caso de uso prático do declaration merging é estender interfaces globais fornecidas por bibliotecas, como estender o objeto Window no navegador ou adicionar propriedades ao objeto process do Node.js.

// Estendendo a interface Window do navegador
declare global {
  interface Window {
    meuApp: {
      versao: string;
      inicializar(): void;
    };
  }
}

// Agora você pode usar:
window.meuApp = {
  versao: "1.0.0",
  inicializar() {
    console.log("Aplicação iniciada");
  }
};

// Sem declaration merging, TypeScript reclamaria que
// 'meuApp' não existe em Window

Conclusão

Interfaces em TypeScript são muito mais que simples documentação: são contratos que garantem segurança de tipo e melhor manutenibilidade. A capacidade de estender interfaces permite construir hierarquias de tipos reutilizáveis e escaláveis, enquanto o declaration merging oferece flexibilidade para integrar e estender código de múltiplas fontes sem duplicação. Dominando esses três pilares — definição, extensão e merging — você terá ferramentas poderosas para escrever código TypeScript profissional e resiliente.

Referências


Artigos relacionados