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:
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 avisouA mesma função em 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 argumentosOs 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
npm install -D typescript
npx tsc --init # cria o tsconfig.json
npx tsc # compila
npx tsc --noEmit # só verifica os tipos, sem gerar arquivosFrameworks 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
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 opcionalInferência
Na maioria das vezes você não precisa escrever o tipo: o TypeScript deduz.
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
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
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:
Uma convenção comum: interface para formatos de objeto públicos, type para uniões e composições.
interface Admin extends Usuario {
permissoes: string[];
}
type UsuarioComEndereco = Usuario & { endereco: string };Uniões e literais
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ãoUniõ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:
const PAPEIS = ["admin", "editor", "leitor"] as const;
type Papel = (typeof PAPEIS)[number]; // "admin" | "editor" | "leitor"Funções
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:
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.
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:
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:
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.
function primeiro<T>(lista: T[]): T | undefined {
return lista[0];
}
const n = primeiro([1, 2, 3]); // number | undefined
const s = primeiro(["a", "b"]); // string | undefinedRestrições
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 usuarioGenerics em tipos
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:
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
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"]; // stringTipos mapeados
Criam um tipo percorrendo as chaves de outro:
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
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>; // numberO infer captura parte de um tipo, e é assim que utilitários como ReturnType são implementados.
Template literal types
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:
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
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:
{
"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
}
}strictliga o conjunto de checagens rigorosas (inclusivestrictNullChecks). É o mais importante.noUncheckedIndexedAccessfazlista[0]ter tipoT | 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
strictdesde o primeiro dia; migrar depois dá muito mais trabalho. - Evite
any. Useunknowne 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
ascom parcimônia: cadaasé um lugar onde você desligou a checagem.
Tabela completa de TypeScript
TypeScript: referência rápida
Sintaxe e utilitários do dia a dia.
Tipos básicos
O essencial
Objetos e funções
Descrevendo formatos
Generics e avançados
Tipos que geram tipos
Utilitários
Transformações prontas
Compilador
tsc no terminal
Referências
- Documentação oficial do TypeScript: o Handbook completo.
- TypeScript Playground: teste código no navegador.
- Referência do tsconfig: cada opção explicada.
- Type Challenges: exercícios de tipos avançados.
- Total TypeScript: tutoriais gratuitos e aprofundados.
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.



