TypeScript é JavaScript com um sistema de tipos. Você escreve praticamente o mesmo código, adiciona informações sobre o formato dos dados, e o compilador aponta erros antes do código rodar. No fim, tudo vira JavaScript comum: os tipos existem só durante o desenvolvimento.

Este guia vai do primeiro let nome: string até tipos condicionais e mapeados, sempre com exemplos que você pode colar no TypeScript Playground e testar.

Por que usar TypeScript

Considere uma função JavaScript simples:

JavaScript
function calcularDesconto(preco, desconto) {
  return preco - preco * desconto;
}
 
calcularDesconto("100", "0.1"); // 90 (por coerção de tipos... por sorte)
calcularDesconto(100);          // NaN, e ninguém avisou

A mesma função em TypeScript:

TypeScript
function calcularDesconto(preco: number, desconto: number): number {
  return preco - preco * desconto;
}
 
calcularDesconto("100", "0.1"); // Erro: string não é atribuível a number
calcularDesconto(100);          // Erro: esperava 2 argumentos

Os ganhos no dia a dia:

  • Erros encontrados no editor, não em produção.
  • Autocomplete de verdade: o editor sabe quais propriedades cada objeto tem.
  • Refatoração segura: renomeie um campo e o compilador mostra cada lugar que quebrou.
  • Documentação viva: a assinatura de uma função diz o que ela recebe e devolve.

Configurando um projeto

Bash
npm install -D typescript
npx tsc --init          # cria o tsconfig.json
npx tsc                 # compila
npx tsc --noEmit        # só verifica os tipos, sem gerar arquivos

Frameworks como Next.js, Vite e NestJS já vêm com TypeScript configurado. Versões recentes do Node.js também conseguem executar arquivos .ts removendo os tipos na hora, mas a checagem de tipos continua sendo feita pelo tsc.

Tipos básicos

TypeScript
let nome: string = "Gabriel";
let idade: number = 28;
let ativo: boolean = true;
let nada: null = null;
let indefinido: undefined = undefined;
 
let numeros: number[] = [1, 2, 3];
let nomes: Array<string> = ["Ana", "João"];
 
let ponto: [number, number] = [10, 20];        // tupla: tamanho e tipos fixos
let registro: [string, number, boolean?] = ["id", 1];  // último elemento opcional

Inferência

Na maioria das vezes você não precisa escrever o tipo: o TypeScript deduz.

TypeScript
let cidade = "São Paulo";   // string
const pi = 3.14;            // o literal 3.14 (const nunca muda)
const lista = [1, 2, 3];    // number[]

A regra prática: anote parâmetros de funções e deixe o resto ser inferido.

any, unknown e never

TipoSignificadoQuando usar
anyDesliga a checagemQuase nunca; é uma saída de emergência
unknown"Pode ser qualquer coisa, prove antes de usar"Dados externos: JSON, respostas de API, catch
neverUm valor que nunca existeFunções que sempre lançam erro; checagem exaustiva
voidFunção que não retorna valor útilCallbacks, handlers
TypeScript
function processar(valor: unknown) {
  // valor.toUpperCase();  // Erro: precisa verificar o tipo antes
  if (typeof valor === "string") {
    return valor.toUpperCase(); // aqui o TypeScript sabe que é string
  }
}

Objetos: type e interface

TypeScript
interface Usuario {
  id: number;
  nome: string;
  email: string;
  telefone?: string;          // opcional
  readonly criadoEm: Date;    // não pode ser alterado
}
 
type Produto = {
  id: number;
  nome: string;
  preco: number;
};

Na prática, os dois descrevem objetos quase igualmente bem. As diferenças:

interfacetype
Descreve objetosSimSim
Uniões, tuplas, primitivosNãoSim
ExtensãoextendsInterseção &
Pode ser reaberta (declaration merging)SimNão

Uma convenção comum: interface para formatos de objeto públicos, type para uniões e composições.

TypeScript
interface Admin extends Usuario {
  permissoes: string[];
}
 
type UsuarioComEndereco = Usuario & { endereco: string };

Uniões e literais

TypeScript
type Status = "pendente" | "pago" | "cancelado";
type Id = string | number;
 
function atualizar(status: Status) {}
 
atualizar("pago");      // ok
atualizar("enviado");   // Erro: não faz parte da união

Uniões de literais substituem com vantagem muitos usos de enum, sem gerar código extra no JavaScript final. Para um objeto de constantes com tipos derivados, use as const:

TypeScript
const PAPEIS = ["admin", "editor", "leitor"] as const;
type Papel = (typeof PAPEIS)[number]; // "admin" | "editor" | "leitor"

Funções

TypeScript
function somar(a: number, b: number): number {
  return a + b;
}
 
const multiplicar = (a: number, b: number = 2) => a * b; // parâmetro com valor padrão
 
function saudar(nome: string, sobrenome?: string) {        // opcional
  return sobrenome ? `Olá, ${nome} ${sobrenome}` : `Olá, ${nome}`;
}
 
function soma(...numeros: number[]) {                      // rest
  return numeros.reduce((total, n) => total + n, 0);
}
 
type Callback = (erro: Error | null, dados?: string) => void;

Sobrecargas

Quando o tipo de retorno depende do tipo de entrada:

TypeScript
function formatar(valor: string): string;
function formatar(valor: number): string;
function formatar(valor: string | number): string {
  return typeof valor === "number" ? valor.toFixed(2) : valor.trim();
}

Narrowing: estreitando tipos

O TypeScript acompanha o fluxo do código e estreita o tipo dentro de cada ramo.

TypeScript
function tamanho(valor: string | string[] | null) {
  if (valor === null) return 0;          // aqui: null
  if (typeof valor === "string") {
    return valor.length;                  // aqui: string
  }
  return valor.length;                    // aqui: string[]
}

Formas de estreitar: typeof, instanceof, in ("email" in usuario), comparações e type guards próprios:

TypeScript
interface Gato { miar(): void }
interface Cachorro { latir(): void }
 
function ehGato(animal: Gato | Cachorro): animal is Gato {
  return "miar" in animal;
}

Uniões discriminadas e checagem exaustiva

O padrão mais útil do TypeScript para modelar estados:

TypeScript
type Resultado =
  | { status: "carregando" }
  | { status: "sucesso"; dados: string[] }
  | { status: "erro"; mensagem: string };
 
function render(r: Resultado) {
  switch (r.status) {
    case "carregando":
      return "Carregando...";
    case "sucesso":
      return r.dados.join(", ");   // dados só existe aqui
    case "erro":
      return r.mensagem;
    default: {
      const impossivel: never = r; // erro se um novo status não for tratado
      return impossivel;
    }
  }
}

Se alguém adicionar { status: "vazio" } à união, o compilador aponta o switch que ficou incompleto.

Generics

Generics são parâmetros de tipo: permitem escrever código reutilizável sem perder a informação de tipo.

TypeScript
function primeiro<T>(lista: T[]): T | undefined {
  return lista[0];
}
 
const n = primeiro([1, 2, 3]);         // number | undefined
const s = primeiro(["a", "b"]);        // string | undefined

Restrições

TypeScript
function maisLongo<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}
 
function pegar<T, K extends keyof T>(obj: T, chave: K): T[K] {
  return obj[chave];
}
 
const usuario = { nome: "Ana", idade: 30 };
pegar(usuario, "nome");    // string
pegar(usuario, "email");   // Erro: "email" não é chave de usuario

Generics em tipos

TypeScript
interface RespostaApi<T> {
  dados: T;
  erro: string | null;
}
 
async function buscar<T>(url: string): Promise<RespostaApi<T>> {
  const res = await fetch(url);
  return { dados: (await res.json()) as T, erro: null };
}
 
const { dados } = await buscar<Usuario[]>("/api/usuarios");

Tipos utilitários

O TypeScript traz transformações prontas:

UtilitárioO que faz
Partial<T>Todas as propriedades opcionais
Required<T>Todas obrigatórias
Readonly<T>Todas somente leitura
Pick<T, "a" | "b">Só algumas propriedades
Omit<T, "senha">Todas menos algumas
Record<K, V>Objeto com chaves K e valores V
Exclude<U, X>Remove membros de uma união
Extract<U, X>Mantém só membros compatíveis
NonNullable<T>Remove null e undefined
ReturnType<F>Tipo de retorno de uma função
Parameters<F>Tupla dos parâmetros
Awaited<T>O valor dentro de uma Promise
TypeScript
type UsuarioPublico = Omit<Usuario, "email" | "telefone">;
type AtualizacaoUsuario = Partial<Pick<Usuario, "nome" | "telefone">>;
type PrecoPorProduto = Record<string, number>;

Tipos avançados

keyof, typeof e acesso indexado

TypeScript
const config = { tema: "escuro", idioma: "pt-BR", beta: false };
 
type Config = typeof config;         // { tema: string; idioma: string; beta: boolean }
type ChaveConfig = keyof Config;     // "tema" | "idioma" | "beta"
type Tema = Config["tema"];          // string

Tipos mapeados

Criam um tipo percorrendo as chaves de outro:

TypeScript
type Opcional<T> = { [K in keyof T]?: T[K] };                 // como Partial
type Validadores<T> = { [K in keyof T]: (valor: T[K]) => boolean };
 
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// Getters<{ nome: string }> = { getNome: () => string }

Tipos condicionais

TypeScript
type EhArray<T> = T extends unknown[] ? true : false;
type ElementoDe<T> = T extends (infer E)[] ? E : T;
 
type A = ElementoDe<string[]>;  // string
type B = ElementoDe<number>;    // number

O infer captura parte de um tipo, e é assim que utilitários como ReturnType são implementados.

Template literal types

TypeScript
type Metodo = "get" | "post";
type Rota = "/usuarios" | "/pedidos";
type Endpoint = `${Uppercase<Metodo>} ${Rota}`;
// "GET /usuarios" | "GET /pedidos" | "POST /usuarios" | "POST /pedidos"

satisfies

Verifica se um valor combina com um tipo sem perder o tipo mais específico inferido:

TypeScript
type Rotas = Record<string, { caminho: string; privada: boolean }>;
 
const rotas = {
  inicio: { caminho: "/", privada: false },
  painel: { caminho: "/painel", privada: true },
} satisfies Rotas;
 
rotas.painel.caminho;   // ok, e o autocomplete conhece "inicio" e "painel"

Com const rotas: Rotas = ..., as chaves virariam string e você perderia o autocomplete.

Classes

TypeScript
abstract class Forma {
  abstract area(): number;
  descrever() {
    return `Área: ${this.area().toFixed(2)}`;
  }
}
 
class Circulo extends Forma {
  constructor(private readonly raio: number) {   // cria e atribui a propriedade
    super();
  }
  area() {
    return Math.PI * this.raio ** 2;
  }
}

Modificadores: public (padrão), private e protected são checados só pelo TypeScript; os campos #privados do JavaScript são privados de verdade em tempo de execução.

Configuração: o tsconfig.json

Um ponto de partida sólido para projetos modernos:

tsconfig.json
JSON
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "exactOptionalPropertyTypes": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true
  }
}
  • strict liga o conjunto de checagens rigorosas (inclusive strictNullChecks). É o mais importante.
  • noUncheckedIndexedAccess faz lista[0] ter tipo T | undefined, como na vida real.
  • moduleResolution: "Bundler" para projetos com Vite, Next.js e similares; para bibliotecas Node publicadas, use "NodeNext".

Boas práticas

  • Ligue strict desde o primeiro dia; migrar depois dá muito mais trabalho.
  • Evite any. Use unknown e estreite o tipo.
  • Prefira uniões discriminadas a vários campos opcionais que "às vezes existem".
  • Não anote o que já é inferido; anote fronteiras (parâmetros, retornos públicos, dados externos).
  • Valide dados que vêm de fora (APIs, formulários, variáveis de ambiente) em tempo de execução.
  • Use as com parcimônia: cada as é um lugar onde você desligou a checagem.

Tabela completa de TypeScript

Cheat sheet

TypeScript: referência rápida

Sintaxe e utilitários do dia a dia.

Tipos básicos

O essencial

ComandoDescrição
let n: numberAnotação de tipo
string[] / Array<string>Arrays
[string, number]Tupla
"a" | "b"União de literais
unknownSeguro: exige verificação
neverValor impossível
as constLiterais imutáveis
T | nullValor que pode faltar

Objetos e funções

Descrevendo formatos

ComandoDescrição
interface U { id: number }Formato de objeto
type P = { nome: string }Alias de tipo
campo?: stringPropriedade opcional
readonly campoSomente leitura
interface B extends AHerança de interface
A & BInterseção
(a: number) => stringTipo de função
valor is TipoType guard

Generics e avançados

Tipos que geram tipos

ComandoDescrição
function f<T>(x: T): TFunção genérica
T extends { id: number }Restrição
K extends keyof TChave válida de T
T[K]Tipo de uma propriedade
{ [K in keyof T]: ... }Tipo mapeado
T extends U ? X : YTipo condicional
infer ECaptura parte de um tipo
valor satisfies TipoCheca sem perder a inferência

Utilitários

Transformações prontas

ComandoDescrição
Partial<T>Tudo opcional
Required<T>Tudo obrigatório
Pick<T, K>Só algumas chaves
Omit<T, K>Sem algumas chaves
Record<K, V>Dicionário tipado
ReturnType<F>Retorno de função
Awaited<T>Valor de uma Promise
NonNullable<T>Sem null/undefined

Compilador

tsc no terminal

ComandoDescrição
npm i -D typescriptInstala no projeto
npx tsc --initCria o tsconfig
npx tscCompila
npx tsc --noEmitSó checa tipos
npx tsc --watchCheca a cada alteração
// @ts-expect-errorEspera um erro na próxima linha

Referências

Teste seus conhecimentos
1 / 5

5 perguntas sobre TypeScript.

Qual a diferença entre any e unknown?

Não foi dessa vez. Explicação:

any desliga a checagem; unknown obriga a estreitar o tipo antes de acessar o valor.