Interfaces e tipos — modelar dados.
Diferenças práticas, quando preferir interface e quando type. Extensão e composição.
Interfaces e type aliases são as duas formas de dar um nome a um tipo em TypeScript. Ambas descrevem a forma de um objeto — e em muitos casos são intercambiáveis. Mas há diferenças práticas que guiam quando usar cada uma. Para o blog, criar interface Artigo, interface Autor e types para status e temas é o primeiro passo para tornar os módulos tipados.
interface para descrever objetos
Uma interface declara a forma que um objeto deve ter — quais propriedades existem e seus tipos. É o principal mecanismo para descrever objetos de domínio:
interface Autor {
id: number;
nome: string;
avatar: string | null;
bio?: string; // opcional — pode não ter bio
}
interface Tag {
slug: string;
label: string;
}
interface Artigo {
id: number;
titulo: string;
descricao: string;
autor: Autor; // composição — Artigo contém Autor
tags: Tag[]; // array de Tag
curtidas: number;
publicado: boolean;
publicadoEm: string | null; // null quando é rascunho
readonly slug: string; // não pode ser alterado após criação
} Propriedades opcionais (?): a propriedade pode estar ausente. TypeScript trata o tipo como TipoOriginal | undefined. Antes de usar, você precisa verificar:
const artigo: Artigo = {
id: 1,
titulo: "CSS em geral",
// autor.bio não é passado — é opcional
autor: { id: 1, nome: "Dev Aprendiz", avatar: null },
// ...demais campos
};
// TypeScript obriga a verificar o campo opcional antes de usar
if (artigo.autor.bio) {
console.log(artigo.autor.bio.toUpperCase()); // ok — bio é string aqui
}
// sem a verificação: TS erro — bio pode ser undefined Propriedades readonly: a propriedade pode ser lida mas não reatribuída depois da criação do objeto. É diferente de const — const impede reatribuição da variável; readonly impede reatribuição da propriedade:
const artigo: Artigo = {
id: 1,
slug: "css-em-geral",
// ...
};
artigo.slug = "outro-slug"; // TS erro: Cannot assign to 'slug' because it is a read-only property
artigo.curtidas = 50; // ok — curtidas não é readonly type aliases
type cria um nome para qualquer tipo — não apenas para objetos. É mais flexível que interface:
// alias para um tipo primitivo com semântica
type Slug = string;
type ID = number;
// union de literais — conjunto fechado de valores
type Tema = "claro" | "escuro";
type StatusArtigo = "rascunho" | "publicado" | "arquivado";
// objeto — equivalente a interface para formas simples
type ReadingTime = {
minutos: number;
palavras: number;
};
// union de objetos — não é possível com interface
type ResultadoBusca =
| { encontrado: true; artigo: Artigo }
| { encontrado: false; mensagem: string };
// intersection — combinar dois tipos
type ArtigoComMetadados = Artigo & {
metadados: {
visualizacoes: number;
compartilhamentos: number;
};
}; Types são essenciais para unions (A | B) e intersections (A & B) — interfaces não suportam essas composições diretamente.
Diferenças práticas
A questão “interface ou type?” tem uma resposta simples para a maioria dos casos:
Use interface para:
- Formas de objetos de domínio (
Artigo,Autor,Tag) - Contratos públicos de módulos — o que o módulo exporta e aceita
- Quando você quer que a forma possa ser estendida com
extends
Use type para:
- Unions (
Tema = "claro" | "escuro") - Intersections (
Artigo & { destacado: boolean }) - Aliases de tipos primitivos com semântica (
Slug,ID) - Mapped types e conditional types (lição 10)
- Quando você quer garantir que o tipo não pode ser reaberto acidentalmente
A razão técnica: interfaces são “abertas” — você pode declarar a mesma interface em dois lugares e TypeScript combina as propriedades (declaração merging). Isso é útil para estender tipos de bibliotecas, mas pode causar confusão em código de aplicação. Types são sempre “fechados” — uma segunda declaração com o mesmo nome é erro.
// interface: pode ser declarada em dois lugares — TypeScript faz o merge
interface Artigo {
id: number;
titulo: string;
}
interface Artigo {
descricao: string; // adicionado — TypeScript combina com a declaração anterior
}
// Artigo agora tem: id, titulo, descricao
// type: segunda declaração é erro
type Artigo = { id: number; titulo: string };
type Artigo = { descricao: string }; // TS erro: Duplicate identifier 'Artigo' Extensão e composição
Interfaces se estendem com extends. Isso cria uma nova interface com todas as propriedades da base mais as novas:
interface ConteudoBase {
id: number;
titulo: string;
slug: string;
publicadoEm: string | null;
}
// Artigo estende ConteudoBase — tem todas as propriedades de base + as suas
interface Artigo extends ConteudoBase {
descricao: string;
autor: Autor;
tags: Tag[];
curtidas: number;
publicado: boolean;
}
// Pagina também usa ConteudoBase — reutilização da forma base
interface Pagina extends ConteudoBase {
conteudo: string;
layout: "padrao" | "largo" | "minimalista";
}
// múltipla extensão
interface ArtigoDestacado extends Artigo, ConteudoBase {
posicaoDestaque: number;
imageDestaque: string;
} Com type, a composição usa intersection (&):
type ArtigoDestacado = Artigo & {
posicaoDestaque: number;
imageDestaque: string;
}; A diferença entre extends e &: extends verifica que a extensão é compatível com a base (um método com o mesmo nome precisa ter tipo compatível). & simplesmente combina os tipos — conflitos resultam em never.
Aplicando ao blog
Com as interfaces definidas, os módulos do blog ficam tipados de ponta a ponta:
// types.ts — exportado e importado por todos os módulos
export interface Autor {
id: number;
nome: string;
avatar: string | null;
bio?: string;
}
export interface Tag {
slug: string;
label: string;
}
export interface Artigo {
id: number;
titulo: string;
descricao: string;
autor: Autor;
tags: Tag[];
curtidas: number;
publicado: boolean;
publicadoEm: string | null;
readonly slug: string;
}
export type Tema = "claro" | "escuro";
export type StatusArtigo = "rascunho" | "publicado" | "arquivado"; import type { Artigo, Autor } from "./types.ts";
export function formatarData(dateString: string): string {
return new Intl.DateTimeFormat("pt-BR").format(new Date(dateString));
}
export function calcularTempoLeitura(palavras: number): number {
return Math.ceil(palavras / 238);
} O import type é uma sintaxe especial: importa apenas os tipos, sem importar o valor em runtime. Como interfaces e types são apagados na compilação, usar import type é mais explícito — e alguns bundlers otimizam esses imports.
Resumo
interfacedescreve a forma de objetos de domínio. Suportaextends, propriedades opcionais (?) ereadonly. É “aberta” — pode ser reaberta com declaração merging.typeé mais flexível — unions, intersections, aliases de primitivos. É “fechado” — uma segunda declaração com o mesmo nome é erro.- Heurística:
interfacepara objetos de domínio e contratos públicos;typepara unions, intersections e transformações. - Propriedade opcional (
bio?: string): tipo éstring | undefined— verificar antes de usar.readonly: a propriedade pode ser lida mas não reatribuída. - Organize os tipos do blog em um
types.tsexportado e importado pelos outros módulos comimport type.
Qual é a principal diferença prática entre interface e type para descrever objetos?
O que é declaração merging em interfaces?
Quando usar type em vez de interface?
Como readonly difere de const para propriedades de objetos?
Aula concluída
Quase lá.