Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA

Guia completo

TypeScript do zero: guia para tipar JavaScript

Entenda como TypeScript encontra erros antes da execução, configure um projeto e escolha entre tipos, interfaces, narrowing e generics sem decorar regras.

5 lições em trilha5 artigos no guiaAtualizado em agosto de 2026

TypeScript é JavaScript com uma etapa de verificação de tipos antes da execução. Você continua escrevendo variáveis, funções, objetos e promessas, mas ganha um compilador capaz de avisar que um preço virou texto, que uma propriedade não existe ou que uma função recebeu o formato errado. Depois dessa conferência, os tipos são removidos e o navegador ou o Node executa JavaScript comum.

O resultado prático não é “um código sem bugs”. É uma mudança no momento em que uma parte importante dos bugs aparece. Em vez de o cliente descobrir no botão de finalizar compra, você vê o problema no editor ou no terminal, enquanto ainda está trabalhando naquela linha. Este guia mostra o modelo mental, a ordem de estudo e as decisões que realmente aparecem num projeto.

Se variáveis, funções e objetos ainda parecem assuntos separados, comece pelo guia de JavaScript. TypeScript não pula essa base: ele coloca contratos em cima dela. Se você já escreve pequenos programas em JavaScript, pode seguir daqui e abrir os exemplos num diretório de teste.

A conferência antes de o pedido sair

Imagine a cozinha de um restaurante. A pessoa monta o pedido, mas antes de o prato ir para a mesa existe uma conferência: número da mesa, itens, restrições e quantidade. Essa conferência não cozinha e não prova que a comida está gostosa. Ela apenas compara o pedido preparado com regras conhecidas e impede alguns enganos previsíveis de seguirem adiante.

O nome técnico dessa conferência é checagem estática de tipos. “Estática” quer dizer que ela analisa o programa sem precisar executar cada caminho. O compilador tsc lê o arquivo .ts, compara valores e operações com os tipos e, se as regras estiverem satisfeitas, emite JavaScript. No mapa da analogia, o pedido é o código-fonte, a ficha de conferência é o tipo, o conferente é o compilador e o prato que de fato sai é o JavaScript.

O limite da comparação é importante: uma ficha correta não garante que o tomate está fresco. Do mesmo modo, TypeScript não sabe se uma API mentiu, se a regra de desconto está errada ou se o servidor caiu. Ele garante coerência entre o que você declarou e o que seu código consegue demonstrar antes de rodar.

Veja um bug que JavaScript aceita sem reclamar:

js
const pedido = {
  subtotal: 120,
  frete: '15',
};

const total = pedido.subtotal + pedido.frete;
console.log(total);
12015

O operador + também junta textos. Como frete é uma string, o resultado vira '12015', não 135. O programa rodou e produziu uma resposta plausível o suficiente para passar despercebida. Agora descreva o formato esperado:

ts
type Pedido = {
  subtotal: number;
  frete: number;
};

const pedido: Pedido = {
  subtotal: 120,
  frete: 15,
};

const total = pedido.subtotal + pedido.frete;
console.log(total);
135

Pedido é um type alias, um nome para um tipo. A tradução simples é “todo valor tratado como Pedido precisa ter subtotal e frete numéricos”. A anotação : Pedido liga esse contrato ao objeto. Ao trocar 15 por '15', a conferência interrompe o fluxo antes de existir um total incorreto.

O contrato do TypeScript em uma frase

Um tipo descreve quais valores são permitidos e quais operações fazem sentido para eles. Se uma variável é string, você pode pedir toUpperCase(). Se é number, pode usar toFixed(). Se é string | number, primeiro precisa provar qual dos dois valores recebeu. Essa descrição ajuda o editor a completar nomes, o compilador a rejeitar combinações inválidas e outra pessoa a entender a intenção da função.

Isso é diferente de converter um valor. Escrever const idade: number não transforma '28' em 28. O sinal : introduz uma anotação de tipo e só faz uma afirmação verificável. Para converter texto em número, você ainda usa JavaScript, por exemplo Number(valor), e decide o que fazer se o resultado for NaN.

Também é diferente de criar uma classe. Um tipo pode descrever um objeto que já existe, sem gerar construtor, método ou qualquer linha extra no JavaScript. Essa separação explica por que você não encontra type, interface ou string no arquivo compilado: eles cumpriram seu trabalho durante o desenvolvimento.

O primeiro erro que você quer ver

Crie um arquivo pedido.ts com um frete textual e peça apenas a verificação:

ts
type Pedido = {
  subtotal: number;
  frete: number;
};

const pedido: Pedido = {
  subtotal: 120,
  frete: '15',
};

console.log(pedido.subtotal + pedido.frete);

Execute o compilador com npx tsc pedido.ts --noEmit. A opção --noEmit pede para checar sem criar o .js. Com TypeScript 7.0.2, a parte central do retorno é:

pedido.ts(8,3): error TS2322: Type 'string' is not assignable to type 'number'.

Leia a mensagem de dentro para fora: foi encontrado string, o contrato exigia number, e a atribuição não é permitida. O código TS2322 identifica essa família de erro; arquivo, linha e coluna apontam onde começar. Não lute para “calar” a mensagem com any. Corrija a origem ou converta e valide a entrada.

Esse erro é uma prova observável da promessa do TypeScript. Faça a primeira microprática agora: troque '15' por 15, rode o mesmo comando e confirme que o terminal termina sem diagnóstico. Depois acrescente console.log, compile sem --noEmit e rode o JavaScript gerado. Você acabou de percorrer as duas etapas: checagem e execução.

Inferir quando está óbvio, anotar quando existe fronteira

TypeScript possui inferência de tipos: ele deduz um tipo a partir do valor e do contexto. Em const cidade = 'Recife', escrever : string repete uma informação já visível. Em uma função pública, porém, os parâmetros formam uma fronteira: vários lugares podem chamá-la, então declarar a entrada e a saída expõe o contrato.

ts
const taxa = 0.1;

function calcularDesconto(total: number, percentual: number): number {
  return total * percentual;
}

const desconto = calcularDesconto(250, taxa);
console.log(desconto.toFixed(2));
25.00

O compilador infere taxa e desconto como números. As anotações dos parâmetros impedem alguém de chamar calcularDesconto('250', '10%'); o retorno explícito protege a intenção caso uma manutenção devolva uma string formatada. Não existe prêmio por colocar : number em todas as três constantes. Um bom tipo mostra a fronteira e deixa o óbvio em silêncio.

Minha regra de projeto é anotar parâmetros de funções exportadas, retornos que carregam regra de negócio e estruturas compartilhadas. Em variáveis locais, prefiro inferência. Essa escolha reduz texto sem abrir mão das garantias onde uma mudança alcança mais gente.

O mapa do cluster em cinco etapas

A ordem abaixo acompanha uma aplicação pequena, não a ordem alfabética dos recursos da linguagem. Primeiro você instala a conferência; depois descreve valores; em seguida nomeia objetos; então trata alternativas; por último cria contratos reutilizáveis.

Etapa Pergunta que resolve Lição prática
1 Como instalar, compilar e organizar a saída? Instalar e configurar TypeScript
2 Como tipar texto, número, lista, objeto e alternativas? Tipos básicos em TypeScript
3 Como escolher entre duas formas de nomear contratos? Interface vs type
4 Como trabalhar com um valor que pode ter mais de um tipo? Narrowing em TypeScript
5 Como reutilizar uma regra sem perder o tipo concreto? Generics em TypeScript

Não tente memorizar a lista inteira antes de escrever um programa. Termine cada lição com o mesmo objeto Pedido e deixe a necessidade puxar o próximo recurso. Quando uma entrada puder ser texto ou número, narrowing deixa de ser teoria. Quando a mesma função precisar preservar Pedido e Cliente, generics ganha um problema real para resolver.

Para ver os cinco assuntos atravessando as fronteiras de uma aplicação, siga o tutorial API Node com TypeScript, Express, Prisma e Docker. O projeto leva os tipos da entrada HTTP até o código que conversa com o banco.

Tipos básicos são o vocabulário do contrato

Os tipos cotidianos começam nos primitivos de JavaScript: string, number, boolean, bigint, symbol, null e undefined. TypeScript acrescenta formas de combiná-los: arrays, objetos, unions, literals e tuples. O nome “básico” não significa descartável; quase todo contrato grande é uma composição dessas peças.

Uma union usa | para dizer “um ou outro”. Um literal type permite um valor específico, não qualquer texto. Juntos, eles descrevem estados conhecidos:

ts
type StatusPedido = 'recebido' | 'pago' | 'enviado';

function mensagem(status: StatusPedido): string {
  if (status === 'recebido') return 'Aguardando pagamento';
  if (status === 'pago') return 'Separando produtos';
  return 'Pedido a caminho';
}

console.log(mensagem('pago'));
Separando produtos

Em JavaScript puro, um erro de digitação como 'paggo' atravessa a chamada e pode cair num ramo errado. No contrato acima, só três textos entram. Na lição de tipos básicos, você monta esse vocabulário aos poucos e compara unknown com o atalho perigoso any.

Interface e type dão nome a formas diferentes

interface e type conseguem descrever muitos dos mesmos objetos. A discussão fica confusa quando vira uma torcida por sintaxe. A pergunta útil é o que você precisa modelar. Interface tem uma natureza aberta e pode ser estendida ou mesclada. Type alias nomeia qualquer tipo, inclusive union, tuple e primitivo, e combina estruturas por interseção.

ts
interface Produto {
  nome: string;
  preco: number;
}

type ItemCarrinho = Produto & {
  quantidade: number;
};

const item: ItemCarrinho = {
  nome: 'Teclado',
  preco: 200,
  quantidade: 2,
};

console.log(`${item.quantidade}x ${item.nome}: R$ ${item.preco * item.quantidade}`);
2x Teclado: R$ 400

Aqui a interface comunica a forma estável de Produto; o type cria uma composição para o carrinho. Poderíamos resolver tudo com um só recurso, mas a mistura é intencional e legível. A lição interface vs type mostra declaração mesclada, extensão, union e o erro estrutural que acontece quando uma propriedade obrigatória falta.

Narrowing transforma possibilidade em certeza local

Quando um valor é string | number, TypeScript impede operações exclusivas de string porque, naquele ponto, o número ainda é possível. Narrowing é o estreitamento do tipo após uma verificação que prova qual caso está presente. Em português direto: você testa a entrada e, dentro daquele caminho, o compilador passa a saber mais sobre ela.

ts
function normalizarCodigo(codigo: string | number): string {
  if (typeof codigo === 'number') {
    return codigo.toString().padStart(6, '0');
  }

  return codigo.trim().toUpperCase();
}

console.log(normalizarCodigo(42));
console.log(normalizarCodigo('  ab-9 '));
000042 AB-9

typeof codigo === 'number' é um type guard, uma verificação reconhecida pelo analisador. No primeiro ramo, codigo é número; depois do retorno, só pode ser texto. Esse conhecimento é local e acompanha o fluxo de controle. A lição de narrowing avança de typeof para in, instanceof, predicados e unions discriminadas.

Generics preservam uma relação entre tipos

Um generic não quer dizer “aceita qualquer coisa e esquece o tipo”. Isso seria any. Ele cria um parâmetro de tipo para preservar uma relação. Se a entrada é Produto, a saída continua Produto; se é Cliente, continua Cliente. A função é reutilizável sem apagar informação.

ts
function primeiro<T>(itens: T[]): T | undefined {
  return itens[0];
}

const primeiroNome = primeiro(['Ana', 'Bia']);
const primeiroNumero = primeiro([10, 20]);

console.log(primeiroNome?.toUpperCase());
console.log(primeiroNumero?.toFixed(2));
ANA 10.00

T é um type parameter. Na primeira chamada ele vira string; na segunda, number. O | undefined representa honestamente a lista vazia. Na lição de generics, você também limita o parâmetro com extends e liga chaves a objetos usando keyof.

Uma configuração pequena, mas deliberada

O arquivo tsconfig.json registra como o compilador deve analisar e emitir o projeto. Comece com o modo estrito, separe fonte e saída e escolha um módulo que combine com seu ambiente. Para um exercício executado como ES modules no Node 24, uma base possível é:

json
{
  "compilerOptions": {
    "target": "ES2024",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "strict": true,
    "rootDir": "src",
    "outDir": "dist",
    "noUncheckedIndexedAccess": true,
    "sourceMap": true
  },
  "include": ["src/**/*.ts"]
}

strict liga uma família de verificações, incluindo cuidado com null e undefined. rootDir aponta onde mora o TypeScript; outDir, onde entra o JavaScript gerado. noUncheckedIndexedAccess faz uma posição de array ou chave dinâmica admitir undefined, porque o item pode não existir. sourceMap ajuda o depurador a apontar o arquivo original.

Não copie configuração como receita universal. Front-end com Vite, biblioteca publicada e API Node têm saídas diferentes. A lição de instalação explica cada passo e mostra como separar npx tsc --noEmit para conferir de npx tsc para emitir. A fonte da verdade de cada opção é a documentação oficial do TSConfig.

Tabela de decisão para um projeto

Depois de ensinar e revisar muitos códigos de iniciantes, minha decisão padrão é esta: inferência no que é local e óbvio; contratos explícitos nas fronteiras; unknown em dado externo; union discriminada para estados; generic apenas quando há uma relação real a preservar. A tabela não é lei da linguagem. É uma política de legibilidade, e você pode ajustá-la quando o projeto trouxer outra pressão.

Situação Escolha inicial Motivo e limite
Constante local com valor claro Inferência Evita repetir string ou number; anote se o contrato precisa ser mais amplo que o valor inicial.
Parâmetro de função exportada Anotação explícita A chamada atravessa arquivos; o contrato merece ficar visível.
Objeto público que pode ser estendido interface Extensão e declaration merging fazem parte do recurso; não use abertura por acidente.
Union, tuple ou combinação de alternativas type Type aliases nomeiam formas que uma interface não representa diretamente.
JSON, formulário ou resposta externa unknown e validação Obriga a provar o formato; a anotação não valida o mundo fora do processo.
Estado com casos conhecidos Union discriminada Cada caso carrega seus próprios dados e o switch pode ser exaustivo.
Função que mantém o tipo da entrada na saída Generic Preserva a relação; se o tipo não influencia outro ponto, talvez não precise de T.
Pressa para silenciar um erro Investigar antes de usar any any desliga a conferência e espalha incerteza para as próximas operações.

Um sinal de tipo bem escolhido é conseguir explicá-lo como regra do domínio: “pedido pago tem data de pagamento”; “esta busca devolve o mesmo tipo de item que recebeu”; “esta entrada ainda não foi validada”. Quando a explicação vira “usei porque o compilador mandou”, pare e volte à informação que o valor representa.

O que os tipos não enxergam

TypeScript verifica o código disponível na compilação. Uma resposta HTTP chega depois, durante a execução. Você pode afirmar que ela é Cliente, mas uma afirmação não muda os bytes recebidos. Se a API devolver { nome: null }, o objeto continuará assim mesmo que uma interface diga nome: string.

ts
type Cliente = {
  nome: string;
};

const texto = '{"nome":null}';
const cliente = JSON.parse(texto) as Cliente;

console.log(cliente.nome);
null

as Cliente é uma type assertion: você diz ao compilador “confie em mim”. Não há validação gerada. Uma fronteira segura começa com unknown, verifica se há objeto, propriedade e tipo correto, e só então devolve Cliente. Bibliotecas de schema automatizam esse trabalho, mas a responsabilidade continua sendo de tempo de execução.

Tipos também não provam regra de negócio. Dois números bem tipados ainda podem ser somados na ordem errada; um desconto pode aceitar 150%; uma data válida pode estar no passado quando deveria estar no futuro. Testes automatizados, validações e revisão continuam complementando a checagem estática.

Três atalhos que cobram a conta depois

O primeiro atalho é espalhar any. Ele não quer dizer “qualquer tipo com segurança”; quer dizer “não confira as próximas operações”. Um valor any passa por parâmetros, propriedades e retornos carregando essa dispensa. Em migração de um projeto grande, pode ser uma ponte temporária, desde que exista um limite visível. Para dado novo e desconhecido, unknown força a prova antes do uso e preserva o propósito da checagem.

O segundo é usar as como conversão. Type assertion é o nome correto, e a palavra assertion importa: você afirma que sabe algo que o compilador não conseguiu demonstrar. Nenhum valor é transformado. Se uma entrada realmente precisa virar número, execute Number, confira Number.isNaN e decida como responder ao valor inválido. Reserve as para situações em que outra evidência garante o formato, mas a análise não consegue representá-la.

O terceiro é instalar tipos e desligar strict sempre que surge atrito. No começo isso parece acelerar a entrega, mas permite que null, parâmetros implícitos e retornos imprecisos atravessem as fronteiras. Quando o projeto cresce, religar a opção transforma um problema localizado em centenas de diagnósticos. Num projeto novo, prefiro começar estrito e resolver a incerteza no momento em que ela nasce.

Há ainda uma distinção operacional: compilador, editor e ferramenta de build não são a mesma coisa. O editor oferece feedback enquanto você digita. O tsc é a referência compartilhável da checagem. Vite, esbuild ou outra ferramenta pode remover a sintaxe TypeScript e criar o bundle sem verificar todos os tipos. Por isso, um build visualmente bem-sucedido não substitui necessariamente tsc --noEmit. Descubra qual ferramenta assume cada etapa no seu projeto e registre a checagem num script que também roda no CI.

Uma microprática útil é procurar any, as e ! no primeiro projeto. Não apague de modo mecânico. Para cada ocorrência, escreva qual evidência justificou o atalho. Se não houver resposta observável, troque por um contrato preciso, um guard ou uma validação de runtime. O objetivo não é zerar operadores; é evitar que uma afirmação sem prova se disfarce de segurança.

A ordem que evita estudar só sintaxe

Instale TypeScript num diretório pequeno e faça um pedido aparecer no terminal. Depois provoque um erro de tipo e leia a mensagem completa. Só então avance para listas, objetos e unions. Essa sequência mantém cada nome técnico ligado a um efeito observável.

Na etapa de interface e type, modele dois objetos do seu projeto em vez de copiar Pessoa e Animal. Em narrowing, faça uma entrada admitir duas formas e trate as duas. Em generics, espere surgir repetição antes de generalizar. O recurso deve responder a uma dor que você já viu no código.

Use a trilha de TypeScript como sequência navegável. Ao concluir cada lição, guarde o código num repositório e faça um commit com o erro antes da correção e outro depois. Esse histórico torna sua aprendizagem revisável: você não precisa confiar na sensação de que entendeu.

Missão: blindar um carrinho pequeno

Crie src/carrinho.ts com um tipo Item que tenha nome, preco e quantidade. Escreva calcularTotal(itens) e formatarResumo(item). Permita que o status do carrinho seja apenas 'aberto', 'pago' ou 'cancelado'. Compile e imprima o total de dois itens.

Depois faça três mudanças controladas: passe preço como texto, retire uma propriedade obrigatória e use o status 'finalizado'. O critério de sucesso é o compilador rejeitar as três alterações antes da execução. Corrija uma de cada vez, rode o JavaScript e confirme o total esperado. Por fim, anote numa frase qual erro TypeScript encontrou e qual regra de negócio ele não seria capaz de encontrar.

Quando essa missão funcionar, você terá o núcleo do método: contrato explícito, erro reproduzido, correção comprovada e limite entendido. A linguagem deixa de ser uma coleção de sinais depois que cada tipo passa a representar uma decisão real do programa.

Trilha

TypeScript

Tipos no JavaScript que você já escreve: o erro aparece no editor, antes do usuário encontrar.

Ver a trilha
  1. 01Como instalar e configurar TypeScript no projeto
  2. 02Tipos básicos em TypeScript: string, number e mais
  3. 03Interface vs type no TypeScript: quando usar cada um
  4. 04Narrowing no TypeScript: como estreitar tipos
  5. 05Generics no TypeScript: reutilize tipos com segurança

Perguntas frequentes

Preciso dominar JavaScript antes de estudar TypeScript?
Você precisa entender variáveis, funções, objetos, arrays e módulos em JavaScript. TypeScript verifica esse mesmo código; ele não substitui a lógica nem as APIs da linguagem que roda no navegador ou no Node.
TypeScript deixa a aplicação mais rápida?
Não por causa dos tipos. Eles são removidos na compilação, então o programa executado continua sendo JavaScript. O ganho principal está no desenvolvimento: erros aparecem antes e mudanças ficam mais seguras.
TypeScript valida os dados que chegam de uma API?
Não sozinho. Uma anotação descreve o que o código espera, mas não inspeciona automaticamente uma resposta externa. Dados de rede, formulário e banco ainda precisam de validação em tempo de execução.
Devo escrever o tipo de toda variável?
Não. Deixe o compilador inferir valores locais óbvios e escreva tipos nas fronteiras: parâmetros, retornos importantes, estado compartilhado e dados externos. Anotação demais transforma informação em ruído.
Interface ou type: qual devo usar por padrão?
Para objetos extensíveis, interface comunica bem a intenção. Para unions, tuples e composição de tipos que não são apenas objetos, use type. Em um objeto simples, consistência do projeto pesa mais que preferência pessoal.

O código deste guia foi executado em Node 24.16.0 com TypeScript 7.0.2, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. TypeScript Handbook — The Basics — typescriptlang.org
  2. TypeScript Handbook — Everyday Types — typescriptlang.org
  3. TypeScript Handbook — Narrowing — typescriptlang.org
  4. TypeScript Handbook — Generics — typescriptlang.org