Anotações e inferência — quando anotar.
Quando anotar e quando deixar TS inferir. A filosofia 'anote nas bordas, infira no meio'.
TypeScript infere tipos automaticamente em muitos casos — quando você escreve const x = 42, o compilador sabe que x é number sem precisar anotar. A arte de usar TypeScript bem está em saber onde a inferência é suficiente e onde uma anotação explícita é necessária (ou desejável). Anotar demais é verboso e redundante; anotar de menos força o compilador a inferir any em lugares críticos.
Quando TypeScript infere automaticamente
A inferência funciona onde o compilador tem informação suficiente no momento da declaração:
// variáveis inicializadas com valor — inferência direta
const titulo = "CSS em geral"; // string
const curtidas = 42; // number
const publicado = true; // boolean
const tags = ["css", "layout"]; // string[]
const artigo = { titulo: "CSS", id: 1 }; // { titulo: string; id: number }
// funções — retorno é inferido do return
function dobrar(x: number) {
return x * 2; // TypeScript infere: number
}
// callbacks — tipo do parâmetro é inferido pelo contexto
const titulos = artigos.map(artigo => artigo.titulo);
// artigo é inferido como Artigo porque artigos é Artigo[]
// titulos é inferido como string[] O TypeScript usa o tipo do array para inferir o tipo do parâmetro em callbacks de map, filter, find e similares. Isso é chamado de inferência por contexto de tipo — o compilador usa o tipo esperado do lado externo para inferir o tipo do lado interno.
Quando anotar explicitamente
A inferência falha ou fica insatisfatória em situações específicas. Nesses casos, uma anotação explícita é necessária ou desejável:
Parâmetros de função: TypeScript não infere o tipo de parâmetros pelo contexto de chamada. Eles precisam de anotação — ou o compilador reclama (com noImplicitAny) ou atribui any silenciosamente (sem strict).
// TS erro com noImplicitAny: parâmetro 'palavras' tem tipo any implícito
function calcularTempoLeitura(palavras) {
return Math.ceil(palavras / 238);
}
// correto: anotar o parâmetro
function calcularTempoLeitura(palavras: number): number {
return Math.ceil(palavras / 238);
} Variáveis declaradas sem valor inicial: quando você declara uma variável e atribui o valor depois, TypeScript não tem como inferir o tipo:
// sem anotação: TypeScript infere any — perde toda a proteção
let statusCarregamento;
statusCarregamento = "carregando"; // sem erro, mas sem proteção
// com anotação: o tipo está definido desde a declaração
let statusCarregamento: "carregando" | "sucesso" | "erro";
statusCarregamento = "carregando"; // ok
statusCarregamento = "desconhecido"; // TS erro: não é um dos três valores válidos Retorno de funções públicas: tecnicamente, TypeScript pode inferir o tipo de retorno da maioria das funções. Mas em funções que fazem parte de uma API pública do módulo, anotar o retorno serve como documentação e como contrato — se a implementação mudar de forma que o tipo de retorno mude, o compilador avisa:
// sem anotação: TypeScript infere o retorno, mas não verifica se está correto
function buscarArtigo(id: number) {
// se por engano você retornar undefined sem querer,
// o tipo inferido fica Artigo | undefined — silenciosamente
return artigos.find(a => a.id === id);
}
// com anotação: o compilador verifica que o retorno bate com a promessa
function buscarArtigo(id: number): Artigo {
// TS erro: Type 'Artigo | undefined' is not assignable to type 'Artigo'
// você é forçado a tratar o caso onde find retorna undefined
return artigos.find(a => a.id === id)!; // ou: lançar erro, ou tratar
}
// alternativa honesta: declarar que pode não encontrar
function buscarArtigo(id: number): Artigo | undefined {
return artigos.find(a => a.id === id);
} Widening — o comportamento de let
TypeScript trata const e let de forma diferente na inferência:
// const: o valor não pode mudar — TypeScript infere o tipo literal
const tema = "claro"; // tipo: "claro" (literal), não string
const curtidas = 42; // tipo: 42 (literal), não number
// let: o valor pode ser reatribuído — TypeScript infere o tipo amplo
let tema = "claro"; // tipo: string (qualquer string, não só "claro")
let curtidas = 42; // tipo: number (qualquer number, não só 42)
tema = "escuro"; // ok — tipo é string Esse comportamento faz sentido: com const, o valor é imutável, então o tipo mais preciso possível (o literal) é correto. Com let, o valor pode mudar, então o compilador usa o tipo mais amplo que engloba o valor inicial.
Para forçar o tipo literal em uma variável let, use as const ou anote explicitamente:
// anotação explícita
let tema: "claro" | "escuro" = "claro";
tema = "escuro"; // ok
tema = "noturno"; // TS erro: não é um dos dois valores válidos
// as const — só funciona na declaração inicial
let tema = "claro" as const; // tipo: "claro" — mas let não ajuda aqui
// (neste caso, use const mesmo) A filosofia: anotar nas bordas, inferir no meio
A regra prática é simples: anote nas bordas do sistema, deixe TypeScript inferir no meio.
Bordas são os pontos onde dados entram ou saem de um módulo:
- Parâmetros de funções exportadas
- Tipos de retorno de funções exportadas
- Interfaces de objetos de domínio (
Artigo,Autor) - Respostas de API
Interior é onde a lógica acontece — variáveis temporárias, callbacks de map/filter, resultados intermediários. Aqui a inferência é suficiente e anotações seriam redundantes:
// interfaces de domínio: anotar — são a fronteira do módulo
interface Artigo {
id: number;
titulo: string;
descricao: string;
tags: string[];
curtidas: number;
publicado: boolean;
publicadoEm: string | null;
}
// funções exportadas: anotar parâmetros e retorno
export function renderizarCards(artigos: Artigo[], curtidos: number[]): string {
// aqui dentro: inferir
const publicados = artigos.filter(a => a.publicado); // Artigo[] — inferido
const html = publicados.map(artigo => { // string[] — inferido
const curtido = curtidos.includes(artigo.id); // boolean — inferido
return `<article data-id="${artigo.id}">...</article>`;
});
return html.join("");
} Resumo
- TypeScript infere tipos automaticamente de variáveis com valor inicial, retornos de função e parâmetros de callbacks (por contexto de tipo). Não é necessário anotar nesses casos.
- Parâmetros de função não são inferidos — sempre precisam de anotação.
- Variáveis sem valor inicial precisam de anotação, ou o compilador não tem como saber o tipo.
- Com
const, TypeScript infere o tipo literal ("claro"). Comlet, infere o tipo amplo (string). Isso é widening — comportamento intencional que reflete a mutabilidade. - A filosofia: anote nas bordas (parâmetros de funções exportadas, interfaces de domínio, respostas de API) e deixe inferir no interior (variáveis temporárias, callbacks, resultados intermediários).
Por que parâmetros de função precisam de anotação de tipo?
Quando faz sentido anotar o tipo de retorno de uma função mesmo que TypeScript possa inferi-lo?
O que acontece quando TypeScript infere o tipo de um elemento de array em map()?
O que é widening de tipo?
Aula concluída
Quase lá.