DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on

TypeScript: Interfaces

Introdução

As interfaces são uma das pedras fundamentais do Typescript. Elas descrevem formas que um objeto deve ter — um contrato que qualquer valor implemente aquela interface precisa respeitar.

Types vs Interfaces

Essa é a dúvida mais comum entre desenvolvedores TypeScript. As duas construções parecem fazer a mesma coisa na maioria dos casos, mas têm diferenças importantes.

A regra prática é:

Objetos públicos, contratos de classe  interface
Unions, primitivos, mapped types, composições  type
Enter fullscreen mode Exit fullscreen mode

Ambas descrevem a forma de um objeto:

type Usuario = {
  id: number;
  nome: string;
};

interface Usuario {
  id: number;
  nome: string;
}
Enter fullscreen mode Exit fullscreen mode

Na prática, para objetos simples, são intercambiáveis (trocado, substituído).

Diferença 1 — Declaration Merging

Interfaces podem ser declaradas múltiplas vezes e o TypeScript mescla automaticamente:

interface Configuracao {
  host: string;
}

interface Configuracao {
  porta: number;
}

// TypeScript mescla as duas:
// { host: string; porta: number }
const config: Configuracao = {
  host: "localhost",
  porta: 5432
};
Enter fullscreen mode Exit fullscreen mode

Com type, isso gera erro:

type Configuracao = { host: string };
type Configuracao = { porta: number }; // ❌ identificador duplicado
Enter fullscreen mode Exit fullscreen mode

Declaration merging é especialmente útil para estender libs externas sem modificar o código delas.

Diferença 2 — Tipos compostos

type consegue representar qualquer coisa, interface só representa objetos:

// só type consegue fazer isso:
type ID = string | number;
type Status = "ativo" | "inativo";
type Nullable<T> = T | null;
type Par<T> = [T, T];
Enter fullscreen mode Exit fullscreen mode

Diferença 3 — Extends vs Intersecção

// interface usa extends
interface Animal { nome: string }
interface Cachorro extends Animal { raca: string }

// type usa &
type Animal = { nome: string }
type Cachorro = Animal & { raca: string }
Enter fullscreen mode Exit fullscreen mode

Funcionalmente equivalentes, mas extends em interface gera mensagens de erro mais claras.

Declaração de Interfaces

Interface declaration é a sintaxe formal de declarar uma interface — mas vai além de propriedades simples. Interfaces suportam vários tipos de membros.

Propriedades

interface Produto {
  id: number;           // obrigatória
  descricao?: string;   // opcional
  readonly codigo: string; // somente leitura
}
Enter fullscreen mode Exit fullscreen mode

readonly impede atribuição após a criação do objeto.

Métodos

interface Repositorio<T> {
  buscarPorId(id: string): Promise<T>;
  salvar(entidade: T): Promise<T>;
  deletar(id: string): Promise<void>;
  listar(): Promise<T[]>;
}
Enter fullscreen mode Exit fullscreen mode

Call Signatures

interface Validador {
  (valor: string): boolean;
}

const validarCNPJ: Validador = (cnpj) => cnpj.length === 14;
Enter fullscreen mode Exit fullscreen mode

Implementação em classes

interface Transmissor {
  transmitir(payload: unknown): Promise<Protocolo>;
  consultar(protocolo: string): Promise<Status>;
}

class TransmissorReinf implements Transmissor {
  async transmitir(payload: unknown): Promise<Protocolo> {
    // implementação
  }

  async consultar(protocolo: string): Promise<Status> {
    // implementação
  }
}
Enter fullscreen mode Exit fullscreen mode

Se a classe não implementar algum método da interface, o TypeScript acusa erro em tempo de compilação.

Extendendo interfaces

Interfaces podem ser estendidas para criar hierarquias de contratos sem duplicar código.

Extends simples

interface EntidadeBase {
  id: string;
  criadoEm: Date;
  atualizadoEm: Date;
}

interface Tenant extends EntidadeBase {
  cnpj: string;
  razaoSocial: string;
}

interface Evento extends EntidadeBase {
  tipo: string;
  competencia: string;
}
Enter fullscreen mode Exit fullscreen mode

Extends múltiplo

Uma interface pode estender várias ao mesmo tempo:

interface ComAuditoria {
  criadoPor: string;
  atualizadoPor: string;
}

interface ComSoftDelete {
  deletadoEm?: Date;
  ativo: boolean;
}

interface EntidadeCompleta extends ComAuditoria, ComSoftDelete {
  id: string;
}

// EntidadeCompleta tem tudo:
// id, criadoPor, atualizadoPor, deletadoEm, ativo
Enter fullscreen mode Exit fullscreen mode

Sobrescrevendo propriedades

Você pode sobrescrever uma propriedade herdada, mas o novo tipo precisa ser compatível com o original:

interface Base {
  id: string | number;
}

interface Derivada extends Base {
  id: string; // ✅ string é subconjunto de string | number
}

interface Invalida extends Base {
  id: boolean; // ❌ boolean não é compatível com string | number
}
Enter fullscreen mode Exit fullscreen mode

Caso prático — sistema fiscal

interface EntidadeBase {
  id: string;
  criadoEm: Date;
  atualizadoEm: Date;
  ativo: boolean;
}

interface EventoFiscal extends EntidadeBase {
  tenantId: string;
  competencia: string;
  status: "pendente" | "processando" | "concluido" | "erro";
}

interface EventoEFDReinf extends EventoFiscal {
  codigoEvento: string;
  cnpjContribuinte: string;
}

interface EventoEFinanceira extends EventoFiscal {
  rubrica: string;
  tipoMovimento: string;
}
Enter fullscreen mode Exit fullscreen mode

Tipos Híbridos

Hybrid types são interfaces que descrevem um valor que é simultaneamente uma função e um objeto. Parece incomum, mas existe em várias libs JavaScript.

Hybrid types são raramente necessários em código novo — são mais úteis para tipar libs JavaScript existentes que foram escritas antes do TypeScript existir. Em código novo, prefira separar a função do objeto explicitamente.

Em JavaScript, funções são objetos — então uma função pode ter propriedades:

function contador() { ... }
contador.total = 0;        // função com propriedade
contador.resetar = () => { ... }; // função com método
Enter fullscreen mode Exit fullscreen mode

Interfaces conseguem descrever exatamente isso.

interface Contador {
  (): number;           // call signature — é uma função
  total: number;        // propriedade
  resetar(): void;      // método
}

function criarContador(): Contador {
  let count = 0;

  const contador = function(): number {
    return ++count;
  } as Contador;

  contador.total = 0;
  contador.resetar = () => { count = 0; };

  return contador;
}

const c = criarContador();
c();          // chama como função → 1
c();          // → 2
c.total;      // acessa propriedade
c.resetar();  // chama método
Enter fullscreen mode Exit fullscreen mode

Top comments (0)