Interface TypeScript vs tipo: Diferenças reais, testadas com o compilador
As interfaces e os aliases de tipo se sobrepõem tanto que a escolha muitas vezes parece arbitrária. Aqui está o que cada um pode fazer e o outro não, confirmado por erros reais do compilador, além de uma regra simples que sua equipe pode seguir.
- Para formas de objetos simples, uma interface e um alias de tipo são quase intercambiáveis.
- Somente
typepode nomear uniões, tuplas e outros tipos que não sejam de objetos. - Somente
interfacesuporta mesclagem de declarações, onde duas declarações são combinadas. - Uma regra comum: interfaces para formas de objetos,
typepara todo o resto, aplicadas de forma consistente.
Poucas questões sobre TypeScript causam tanto debate quanto Interface TypeScript versus tipo. Muitos artigos listam diferenças que costumavam ser importantes em versões mais antigas, mas agora estão desatualizadas. Então, em vez de repetir afirmações, nós as testamos.
Escrevemos um arquivo exercitando cada diferença e o verificamos com TypeScript 5.6.3 em modo estrito. Os erros do compilador citados abaixo vêm dessa execução. O comportamento pode mudar entre as versões, portanto, verifique a versão TypeScript que seu projeto usa. Se você é novo na linguagem, comece com nosso tutorial TypeScript para iniciantes.
A resposta curta
Um interface descreve a forma de um objeto e pode ser estendido ou reaberto. Um type alias dá um nome a qualquer tipo, incluindo objetos, uniões, tuplas e tipos de função. Onde ambos podem funcionar, a diferença é principalmente o estilo.
Onde eles são iguais
Estas duas declarações descrevem formas idênticas:
interface User {
id: number;
name: string;
email?: string;
}
type UserAlias = {
id: number;
name: string;
email?: string;
};
const a: User = { id: 1, name: "Asha" };
const b: UserAlias = a; // no error
O TypeScript usa tipagem estrutural, o que significa que compara formas em vez de nomes. É por isso que um User pode ser atribuído a um UserAlias sem reclamação. Ambos suportam propriedades opcionais, readonly propriedades, métodos e assinaturas de índice.
Interface vs tipo lado a lado
| Recurso | interface | tipo |
|---|---|---|
| Formas de objetos | Sim | Sim |
Sindicatos, como "a" | "b" |
Não | Sim |
| Tuplas e primitivas | Não | Sim |
| Tipos mapeados e condicionais | Não | Sim |
| Mesclagem de declarações | Sim | Não, é um erro |
| Estendendo | estende |
& interseção |
| Propriedades conflitantes | Erro na declaração | Pode silenciosamente se tornar never |
Usado com implements |
Sim | Sim, se for um tipo de objeto |
O que só o tipo pode fazer
Muitos tipos úteis não são formas de objetos e apenas um alias de tipo pode nomeá-los:
type Status = "pending" | "paid" | "shipped"; // union
type Point = [number, number]; // tuple
type Handler = (event: string) => void; // function type
type Keys = keyof User; // computed type
type ReadonlyUser = Readonly<User>; // utility type
Os tipos de união estão entre os recursos mais valiosos do TypeScript, porque o compilador rejeita qualquer valor fora da lista:
let s: Status = "paid";
s = "refunded";
// error TS2322: Type '"refunded"' is not assignable to type 'Status'.
Isso detecta erros de digitação e valores desatualizados em tempo de compilação, antes que cheguem à produção.
Mesclagem de declarações
Se você declarar uma interface com o mesmo nome duas vezes, o TypeScript as mesclará em uma:
interface Settings {
theme: string;
}
interface Settings {
language: string;
}
const settings: Settings = { theme: "dark", language: "en" }; // no error
Tente a mesma coisa com um alias de tipo e o compilador recusa:
type Config = { theme: string };
type Config = { language: string };
// error TS2300: Duplicate identifier 'Config'.
Mesclar é como as bibliotecas permitem estender seus tipos. Por exemplo, você pode adicionar uma propriedade personalizada à interface global Window ou ao objeto de solicitação de uma estrutura, sem modificar a biblioteca. Isso é genuinamente útil.
O outro lado é que a fusão pode acontecer por acidente. Se dois arquivos em um projeto declaram uma interface com o mesmo nome no mesmo escopo, eles se combinam silenciosamente, o que pode ser confuso. Um alias de tipo torna essa colisão um erro.
estende vs interseções
Ambos permitem desenvolver um tipo existente:
interface Admin extends User {
permissions: string[];
}
type AdminAlias = UserAlias & { permissions: string[] };
Para propriedades compatíveis, o resultado é o mesmo. A diferença aparece com um conflito. Quando uma interface tenta alterar o tipo de uma propriedade de forma incompatível, o compilador reporta isso imediatamente:
interface Base { id: number }
interface Broken extends Base { id: string }
// error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
// Types of property 'id' are incompatible.
// Type 'string' is not assignable to type 'number'.
Um cruzamento não reclama quando é declarado. Em vez disso, a propriedade conflitante se torna never, um tipo que nenhum valor pode ter. O erro só aparece mais tarde, quando você tenta utilizá-lo:
type Mixed = { id: number } & { id: string };
const m: Mixed = { id: 1 };
// error TS2322: Type 'number' is not assignable to type 'never'.
Esse erro posterior é mais confuso, pois aponta para longe do erro real. Esta é uma razão prática para preferir extends ao construir tipos de objetos.
Aulas e implementos
Uma classe pode prometer corresponder a uma forma usando implements, e isso funciona com uma interface ou um alias de tipo de objeto:
class Account implements User {
constructor(public id: number, public name: string) {}
}
Isto compilado sem erros. Observe que implements apenas verifica se a classe corresponde. Ele não adiciona nenhuma propriedade ou código. Uma classe não pode implementar um tipo de união, pois não existe uma forma única para corresponder.
Somente leitura e tipos de utilitários
Tipos de utilitários como Readonly, Partial, Pick e Omit funcionam com interfaces e aliases. O resultado é um novo tipo, que normalmente é nomeado com um alias de tipo:
const r: ReadonlyUser = { id: 3, name: "Dev" };
r.name = "Chitra";
// error TS2540: Cannot assign to 'name' because it is a read-only property.
Lembre-se de que readonly é apenas uma verificação em tempo de compilação. Em tempo de execução, o objeto é JavaScript comum e ainda pode ser alterado por código que ignora os tipos.
Dica: Os tipos desaparecem quando o TypeScript é compilado para JavaScript. Os dados que chegam de fora, como uma resposta da API, não são verificados em tempo de execução apenas porque possuem um tipo. Valide-o, por exemplo, com uma biblioteca de esquemas. Nosso tutorial Fetch API aborda o tratamento seguro das respostas da API.
Desempenho do compilador
A orientação de desempenho da própria equipe do TypeScript sugere preferir interfaces que usam extends em vez de interseções grandes, porque o compilador pode armazenar em cache os relacionamentos de interface com mais eficiência. Em projetos pequenos e médios é improvável que você note alguma diferença. Pode ser importante em bases de código muito grandes com tipos profundamente compostos. Nós mesmos não medimos isso.
Qual você deve usar?
Uma regra amplamente utilizada, de acordo com as orientações do manual TypeScript, é:
- Use
interfacepara formas de objetos, especialmente APIs públicas e qualquer coisa que as classes implementem ou outras possam estender. - Use
typepara uniões, tuplas, tipos de função e tipos mapeados ou computados.
| Você está definindo | Usar |
|---|---|
| Um modelo de dados, como Usuário ou Pedido | interface |
| Um conjunto de valores permitidos | type união |
| Um par ou triplo de comprimento fixo | type tupla |
| Uma assinatura de retorno de chamada | tipo |
| Estendendo os tipos de uma biblioteca | interface, através da fusão |
| Uma variação de um tipo existente | type com um tipo de utilitário |
Algumas equipes simplesmente usam type em todos os lugares, e isso também funciona bem. O que mais importa é a consistência, então escolha uma convenção, escreva-a em seu guia de estilo e deixe um linter aplicá-la. A regra consistent-type-definitions do ESLint do typescript-eslint pode fazer isso.
Erros comuns
- Debate sem fim. Ambos são bons para objetos, então estabeleça a regra uma vez e siga em frente.
- Mesclagem acidental. A reutilização de um nome de interface no mesmo escopo combina-os silenciosamente.
- Interseções conflitantes. Eles criam
nevertipos com erros confusos posteriormente. - Tipos confiáveis em tempo de execução. Eles são apagados, então valide os dados externos.
- Usar
anypara silenciar erros. Desativa a verificação. Prefiraunknowne restrinja-o.
Alternando entre eles em uma base de código existente
Alterar interface para type, ou o inverso, geralmente é seguro para formas de objetos simples, porque o TypeScript compara os tipos por sua estrutura, não por seus nomes. Algumas coisas ainda precisam ser verificadas antes de você mudar:
- A conversão de uma interface em um alias de tipo remove a mesclagem de declarações, portanto, qualquer segunda declaração com o mesmo nome se torna um erro de identificador duplicado.
- A conversão de um alias de tipo em uma interface funciona apenas para formas de objetos. Uniões, tuplas e tipos mapeados devem permanecer como aliases de tipo.
- Mensagens de erro e atualizações do editor podem exibir o tipo de maneira diferente, o que pode surpreender os colegas de equipe, mas não altera o comportamento.
Faça a mudança em uma solicitação pull focada, execute o compilador no modo estrito e seu conjunto de testes e evite misturá-lo com o trabalho de recursos para que os revisores possam ver que nada mais mudou.
Considerações finais
A questão interface versus tipo tem uma resposta calma: eles se sobrepõem aos objetos, cada um tem algumas habilidades únicas e a consistência é mais importante do que a escolha. Use interfaces para formas e tipos para todo o resto, a menos que sua equipe prefira o contrário. Para ver as diferenças você mesmo, copie os exemplos acima em um arquivo e execute tsc --noEmit --strict nele.
Perguntas frequentes
Qual é a diferença entre interface e tipo no TypeScript?
Ambos podem descrever formas de objetos. Somente o tipo pode nomear uniões, tuplas e tipos computados, enquanto apenas a interface suporta a fusão de declarações.
Devo usar interface ou tipo para acessórios React?
Qualquer um funciona bem. Muitas equipes usam interfaces para adereços, enquanto outras preferem type para consistência com uniões. Escolha um e aplique-o em todo o projeto.
Um tipo pode estender uma interface?
Sim, usando uma interseção como type Admin = User & { permissions: string[] }. Uma interface também pode estender um alias de tipo de objeto.
A interface é mais rápida que o tipo?
A orientação de desempenho do TypeScript favorece a extensão da interface por grandes interseções em grandes bases de código. Em projetos típicos, é improvável que a diferença seja perceptível.
O que é mesclagem de declarações?
É quando duas interfaces com o mesmo nome no mesmo escopo se combinam em uma. Os aliases de tipo não podem ser mesclados e nomes duplicados causam um erro.
Continue aprendendo
Precisa de ajuda com testes, WordPress ou SEO?
Ajudamos as equipes a fornecer software melhor e a expandir seus sites. Conte-nos no que você está trabalhando e sugeriremos um próximo passo claro.
Mais guias sobre testes, IA e desenvolvimento moderno
Gostou deste artigo? Escolha um tópico e vá direto para nossos tutoriais mais recentes.
Explicação das alucinações de IA: por que os modelos de linguagem inventam as coisas e como reduzi-las
Explicação das alucinações de IA: o que são, por que os modelos de linguagem as produzem, tipos comuns, como reduzi-las com aterramento e instruções…
Leia o artigo →DevOpsVariáveis de ambiente explicadas: arquivos .env, segredos e configuração bem feitos
Variáveis de ambiente explicadas com exemplos de Python que executamos: leitura e configuração, por que cada valor é uma string, arquivos .env, hera…
Leia o artigo →Desenvolvimento de softwareExplicação da limitação de taxa de API: algoritmos, cabeçalhos e tratamento de erros 429
Limitação de taxa de API explicada com uma simulação que executamos: token bucket, janela fixa, janela deslizante, 429 respostas, cabeçalhos de limi…
Leia o artigo →Automação de testesZombando em Python: unittest.mock, patch e os erros que fazem os testes mentirem
Zombaria em Python com unittest.mock, testado: Mock e return_value, side_effect, patch onde um nome é usado, especificação e autospec, zombaria de d…
Leia o artigo →Teste de softwareCobertura de código explicada: cobertura de linha versus filial e o que 100% realmente significa
Cobertura de código explicada com uma execução cobertura.py real: cobertura de linha, ramal e condição, leitura de um relatório, por que 100% de cob…
Leia o artigo →Bancos de dados e SQLDELETE vs TRUNCATE vs DROP em SQL: diferenças, reversão e quando usar cada um
DELETE vs TRUNCATE vs DROP explicado com SQL testado: o que cada um remove, rollback, aciona, redefinição de identidade, velocidade e como PostgreSQ…
Leia o artigo →Bancos de dados e SQLExibições SQL explicadas: crie, use e evite as armadilhas, com exemplos
Visualizações SQL explicadas com consultas que executamos: criação de uma visualização, resultados ao vivo, ocultação de colunas, desempenho, visual…
Leia o artigo →Desenvolvimento de softwareInterface TypeScript vs tipo: diferenças reais, testadas com o compilador
Interface TypeScript versus tipo, verificada com o compilador real: o que ambos podem fazer, o que apenas um pode fazer, mesclagem de declarações, e…
Leia o artigo →Desenvolvimento de softwarePython *args e **kwargs explicados: funções flexíveis com exemplos claros
Python *args e **kwargs explicados com o código que executamos: empacotamento e descompactação, ordem dos parâmetros, argumentos somente de palavras…
Leia o artigo →Desenvolvimento de softwareFunções Python Lambda: sintaxe, usos reais e quando não usá-los
Funções lambda do Python explicadas com o código que executamos: sintaxe, classificação com chave, mapa e filtro, fechamentos, armadilha de ligação …
Leia o artigo →Inteligência artificialO que são agentes de IA? Como eles funcionam, com um loop de agente simples que você pode executar
O que são agentes de IA? Um guia simples para o loop do agente, ferramentas, memória e proteções, com um pequeno loop Python que executamos, riscos …
Leia o artigo →DevOpsMonitoramento versus observabilidade: a verdadeira diferença, com logs, métricas e rastreamentos
Explicação do monitoramento versus observabilidade: falhas conhecidas e desconhecidas, logs, métricas e rastreamentos, um passo a passo do incidente…
Leia o artigo →DevOpsDocker Volumes vs Bind Mounts: persista os dados do contêiner da maneira certa
Volumes Docker versus montagens de ligação explicadas: por que os dados do contêiner desaparecem, volumes nomeados, montagens de ligação, tmpfs, exe…
Leia o artigo →Automação de testesTDD vs BDD: principais diferenças, um recurso criado de ambas as maneiras e quando usar cada um
TDD vs BDD explicado com um recurso de desconto construído em ambos os sentidos: refatoração vermelho-verde com o código que executamos, cenários da…
Leia o artigo →Desenvolvimento de softwareGit merge vs rebase: diferenças, exemplos e quando usar cada um
Git merge vs rebase explicado com saída real do log, uma tabela comparativa, a regra de ouro do rebase e um fluxo de trabalho seguro para equipes.
Leia o artigo →Desenvolvimento de softwareList comprehension em Python: sintaxe, exemplos e quando evitar
List comprehension em Python explicada: sintaxe, filtros, if-else, laços aninhados, tempos medidos e quando um laço for comum é a melhor escolha.
Leia o artigo →Bancos de dados e SQLSQL JOIN explicado com exemplos (Inner, Left, Right, Full)
Entenda os joins SQL passo a passo com tabelas e consultas de exemplo: INNER, LEFT, RIGHT, FULL, CROSS e SELF, além de erros comuns e dicas de desem…
Leia o artigo →