O que é TypeScript?
TypeScript é um superset de JavaScript com tipagem estática que adiciona anotações de tipo opcionais verificadas em tempo de compilação. Ele é compilado para JavaScript puro, portanto, roda em qualquer lugar que o JavaScript rode — navegadores, Node.js, Deno e Bun.
Criado pela Microsoft em 2012, o TypeScript tornou-se a escolha padrão para projetos sérios de JavaScript. O motivo é simples: ele captura bugs antes mesmo do seu código ser executado. Um erro de digitação no nome de uma propriedade, um argumento ausente, um tipo de retorno incorreto — o TypeScript sinaliza esses problemas em tempo de compilação, em vez de permitir que eles se tornem crashes em produção.
A melhor maneira de pensar no TypeScript é como um JavaScript com uma rede de segurança. Você escreve a mesma linguagem que já conhece, mas pode descrever o formato dos seus dados e os contratos de suas funções. O compilador verifica se tudo se encaixa e, em seguida, remove os tipos, deixando apenas o JavaScript limpo.
Por que o TypeScript é importante
O JavaScript é dinamicamente tipado, o que significa que você pode passar qualquer valor para qualquer função e o erro só aparecerá quando esse código for executado. Em um script pequeno, isso não é problema. Em uma aplicação grande com dezenas de desenvolvedores, é a receita para bugs em produção.
O TypeScript resolve isso verificando os tipos antes de o código ser executado:
- Captura de bugs em tempo de compilação — se você digitar errado o nome de uma propriedade, passar o tipo de argumento incorreto ou esquecer de tratar um valor null, o TypeScript te avisará imediatamente.
- Melhor experiência no editor — o autocomplete funciona porque o editor sabe quais propriedades um objeto possui. A refatoração é segura porque o compilador encontra todos os lugares que precisam de atualização.
- Código autodocumentado — as assinaturas das funções descrevem exatamente o que elas esperam e o que retornam. Ler os tipos permite entender como usar o código sem precisar ler a implementação.
- Refatoração mais segura — altere um tipo e o compilador destacará todos os pontos que quebraram. Chega de caçar erros de runtime por toda a base de código.
- Adoção progressiva — você pode adicionar TypeScript a um projeto JavaScript existente de forma incremental, um arquivo por vez.
O TypeScript não adiciona overhead de runtime. Os tipos são removidos durante a compilação, portanto, o JavaScript que chega aos usuários é idêntico, independentemente de você ter escrito em TypeScript ou não.
Como o TypeScript funciona
O código TypeScript reside em arquivos .ts. O compilador TypeScript (tsc) lê esses arquivos, verifica os tipos e gera arquivos .js com as anotações de tipo removidas.
// app.ts
function add(a: number, b: number): number {
return a + b;
}
const result = add(1, 2); // result is number
O compilador verifica se a e b são números, se a função retorna um número e se result recebe um número. Se algo não coincidir, você receberá um erro em tempo de compilação. O JavaScript de saída é simplesmente:
function add(a, b) {
return a + b;
}
const result = add(1, 2);
Sem tipos, sem overhead. A segurança ocorreu apenas na etapa de build.
Anotações de tipo
As anotações de tipo descrevem o tipo de uma variável, parâmetro ou valor de retorno:
// primitives
let name: string = "Ada";
let age: number = 36;
let active: boolean = true;
// arrays
let skills: string[] = ["HTML", "CSS"];
let scores: Array<number> = [95, 87, 92];
// function parameters and return
function greet(name: string): string {
return `Hello, ${name}!`;
}
Você nem sempre precisa de anotações. O TypeScript infere os tipos a partir do valor:
let count = 0; // inferred as number
const items = ["a"]; // inferred as string[]
Prefira a inferência quando o tipo for óbvio. Use anotações quando o tipo não estiver claro a partir do valor, quando você precisar de um tipo mais genérico ou quando a inferência padrão for restrita demais.
Inferência de tipos
O TypeScript deduz automaticamente os tipos a partir do contexto:
// from assignment
let x = 5; // number
// from return value
function doubling(n: number) {
return n * 2; // inferred as number
}
// from context
const numbers = [1, 2, 3]; // number[]
const doubled = numbers.map(n => n * 2); // number[]
A inferência significa que você escreve menos código mantendo a mesma segurança. A maior parte do código TypeScript depende fortemente de inferência — anote os tipos apenas quando isso adicionar clareza.
Union types
Um union type indica que um valor pode ser de um entre vários tipos:
type Status = "loading" | "success" | "error";
function handleStatus(status: Status) {
if (status === "loading") {
// TypeScript knows status is "loading" here
}
}
let value: string | number;
value = "hello";
value = 42;
Unions modelam bem dados do mundo real. Respostas de API podem ter sucesso ou falhar. Um campo pode ser uma string ou null. Uma função pode aceitar diferentes tipos de entrada.
Type narrowing
O TypeScript realiza o narrowing (estreitamento) de union types dentro de condicionais:
function process(value: string | number) {
if (typeof value === "string") {
// TypeScript knows value is string here
return value.toUpperCase();
}
// TypeScript knows value is number here
return value.toFixed(2);
}
O narrowing também funciona com:
- Operador
in— verifica se uma propriedade existe instanceof— verifica instâncias de classes- Verificações de igualdade —
===e!== - Discriminated unions — verifica uma propriedade de tag comum
type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; side: number };
type Shape = Circle | Square;
function area(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
}
}
Discriminated unions são um dos padrões mais poderosos do TypeScript. A propriedade kind informa ao TypeScript exatamente com qual variante você está lidando.
Interfaces e type aliases
Uma interface descreve a forma de um objeto — suas propriedades, seus tipos e se elas são obrigatórias. Um type alias atribui um nome a qualquer tipo, incluindo unions, tuples, primitivos e tipos de função.
// Interface
interface User {
id: number;
name: string;
email: string;
}
// Type alias with the same shape
type User = {
id: number;
name: string;
email: string;
};
Para formas de objetos simples, os dois são intercambiáveis. As diferenças aparecem quando você compõe ou estende tipos.
Propriedades opcionais e readonly
interface User {
id: number;
name: string;
email?: string; // optional — string | undefined
readonly createdAt: Date;
}
const user: User = { id: 1, name: "Ada", createdAt: new Date() };
user.createdAt = new Date(); // Error: cannot assign to a readonly property
Estendendo interfaces
Interfaces herdam de outras interfaces com extends, inclusive de várias ao mesmo tempo:
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface User extends Timestamped {
id: number;
name: string;
}
Type aliases para unions, tuples e funções
Os type aliases brilham quando o tipo não é um objeto simples:
type ID = string | number;
type Status = "idle" | "loading" | "success" | "error";
type Pair = [string, number];
type Callback = (data: unknown) => void;
Intersection types
Combine tipos com &:
type User = { id: number; name: string };
type WithTimestamp = { createdAt: Date };
type TimestampedUser = User & WithTimestamp;
Interface ou type?
Use interfaces para formas de objetos que possam ser estendidas ou implementadas por classes. Use type aliases para unions, intersections, tuples e tipos de função. Escolha uma convenção para formas de objetos e mantenha a consistência.
Generics
Generics são parâmetros de tipo: eles permitem que uma função, interface ou classe funcione com qualquer tipo, preservando a segurança de tipos (type safety). Em vez de fixar um tipo ou recorrer ao any, você passa uma variável de tipo que é preenchida no momento da chamada.
function first<T>(items: T[]): T | undefined {
return items[0];
}
const num = first([1, 2, 3]); // number
const str = first(["a", "b"]); // string
Sem generics, você teria que usar any e perder as informações de tipo, ou escrever uma função separada para cada tipo.
Funções genéricas
Um parâmetro de tipo é declarado entre colchetes angulares. O TypeScript geralmente o infere a partir dos argumentos, então raramente você precisará passá-lo explicitamente:
function merge<T, U>(a: T, b: U): T & U {
return { ...a, ...b };
}
const result = merge({ name: "Ada" }, { age: 36 });
// { name: string; age: number }
Interfaces e classes genéricas
interface Response<T> {
data: T;
status: number;
}
interface User {
id: number;
name: string;
}
const response: Response<User> = {
data: { id: 1, name: "Ada" },
status: 200,
};
class Stack<T> {
private items: T[] = [];
push(item: T) { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
}
Constraints
Use extends para restringir o que um generic pode aceitar:
interface HasId {
id: number;
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find((item) => item.id === id);
}
Parâmetros de tipo padrão
interface Paginated<T = unknown> {
data: T[];
total: number;
page: number;
}
const response: Paginated = { data: [1, "a"], total: 10, page: 1 };
Generics são removidos no runtime, portanto não adicionam overhead — eles existem apenas para tornar o compilador e o seu editor mais inteligentes.
O tsconfig.json
O arquivo tsconfig.json configura o compilador TypeScript:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Opções principais:
- target — qual versão do JavaScript será gerada (ES2020 é um bom padrão)
- strict — habilita todas as flags de verificação de tipo rigorosa
- module — o sistema de módulos para a saída (ESNext para projetos modernos)
- outDir — onde o JavaScript compilado será armazenado
- esModuleInterop — permite importações default de módulos CommonJS
Sempre use "strict": true para novos projetos. Ele detecta mais bugs, ao custo de exigir um código ligeiramente mais explícito.
Instalando TypeScript
# As a project dependency (recommended)
npm install --save-dev typescript
# Globally (not recommended for teams)
npm install -g typescript
Compile seu projeto:
# Using npx
npx tsc
# Or add a script to package.json
npm run build
Para desenvolvimento com modo watch:
npx tsc --watch
A maioria dos frameworks modernos (Next.js, Vite, Astro) cuida da compilação do TypeScript para você — não é necessário executar tsc manualmente.
Erros comuns
- Usar
anyem todo lugar, o que desativa a verificação de tipos. - Anotar tudo quando a inferência já conhece o tipo.
- Não habilitar o modo strict no tsconfig.
- Confundir tipos do TypeScript com valores de runtime — os tipos são removidos no momento da compilação.
- Importar tipos incorretamente — use
import typepara importações apenas de tipos. - Ignorar erros do compilador em vez de corrigi-los.
- Não instalar pacotes
@typespara bibliotecas JavaScript. - Tornar todas as propriedades de uma interface opcionais, o que oculta verificações obrigatórias.
- Usar
anyem vez de um generic, descartando a informação de tipo que você queria manter.
Melhores práticas
- Ative o strict mode desde o primeiro dia.
- Deixe a inferência fazer o trabalho — anote os tipos apenas quando isso agregar valor.
- Use
unknownem vez deanyquando você não souber o tipo. - Prefira interfaces para formatos de objetos que possam ser estendidos.
- Use discriminated unions para máquinas de estado e dados variantes.
- Mantenha as definições de tipo próximas de onde são utilizadas.
- Use
import typepara importações apenas de tipo (type-only imports) para manter o código de runtime reduzido. - Trate o compilador como um colega de equipe, não como um obstáculo.
O que aprender a seguir
Agora você já domina os fundamentos do TypeScript: anotações, inferência, unions, narrowing, interfaces, type aliases e generics. A partir daqui, o próximo passo natural são os Advanced Types para aprender sobre utility types, conditional types e mapped types. Escolha um projeto, refatore algum código e deixe a prática consolidar seu conhecimento.