Introdução
Tipos são uma forma de definir a “forma” ou o contrato dos dados que estamos usando no código. Pensando em Javascript puro, ele é dinâmico: você pode criar uma variável com um número e logo depois colocar um texto nela. O Typescript entra pra colocar ordem na casa.
Os tipos dizem ao compilador o que cada variável, função ou objeto pode receber e fazer. Se você tentar fazer algo que quebre essa regra, o TypeScript te avisa antes do código rodar (em tempo de compilação), evitando aqueles bugs clássicos em produção.
Os Principais Tipos no Typescript
Podemos dividir os tipos em algumas categorias principais:
- Primitivos
- Objetos
- Especiais
Primitivos
Boolean
Representa dois estados mutuamente exclusivos. Em TypeScript, boolean é um tipo primitivo que corresponde exatamente ao Boolean primitivo do JavaScript, não ao wrapper object Boolean. O compilador infere boolean automaticamente a partir de expressões lógicas e literais true/false.
// Anotação explícita vs inferência
let ativo: boolean = true;
let inferido = false; // tipo: boolean
// Literal types — subtipos de boolean
const sim: true = true; // tipo: true (não boolean)
const nao: false = false; // tipo: false (não boolean)
// boolean = true | false (union de literais)
type Bool = true | false; // equivalente a boolean
// Narrowing: TS afina o tipo dentro de cada branch
function toggle(v: boolean): boolean {
return !v;
}
// CUIDADO: Boolean wrapper object != boolean primitivo
const errado: Boolean = new Boolean(false); // objeto, não primitivo!
if (errado) { /* sempre truthy! Boolean({}) é truthy */ }
Evite o wrapper object Boolean (com B maiúsculo).
Use sempre o primitivo boolean. O wrapper é um objeto e,
portanto, sempre truthy — mesmo new Boolean(false)
é truthy.
Number
TypeScript (como JavaScript) possui um único tipo numérico: number, que cobre inteiros, floats, hexadecimais, octais, binários e o especial NaN/Infinity. Para inteiros arbitrariamente grandes, use bigint.
// Todas as notações são válidas
let inteiro = 42;
let float = 3.14;
let hex = 0xFF;
let octal = 0o52;
let binario = 0b101010;
let separador = 1_000_000; // ES2021 — legibilidade
let nan: number = NaN;
let inf: number = Infinity;
// Literal types numéricos
type Dado = 1 | 2 | 3 | 4 | 5 | 6;
type Par = 0 | 2 | 4 | 6 | 8;
// bigint — tipo separado para inteiros grandes
const enorme: bigint = 9007199254740993n;
// number + bigint = TypeError em runtime!
number não pode representar com precisão inteiros
maiores que Number.MAX_SAFE_INTEGER (2⁵³−1).
Para IDs de banco de dados grandes, use bigint ou string.
String
Strings são imutáveis em JavaScript/TypeScript. Template literals têm tipo string a menos que o contexto force um literal type. O tipo Template Literal Type permite compor tipos de string estaticamente.
// Sintaxes equivalentes
let a: string = "aspas duplas";
let b: string = 'aspas simples';
let c: string = `template literal`;
// Template literal type (feature de tipo, não runtime)
type EventName = `on${Capitalize<string>}`;
// "onClick", "onChange", "onSubmit" — mas não "onclick"
type Rota = `/${'users' | 'posts' | 'auth'}`;
// "/users" | "/posts" | "/auth"
type CSSVar = `--${string}`;
// Qualquer string começando com "--"
// Literal type vs string widened
const literal = "GET"; // tipo: "GET" (const → literal)
let widened = "GET"; // tipo: string (let → widened)
const forçado = "GET" as const; // tipo: "GET" (explícito)
Void
void é o tipo correto para funções que não produzem valor utilizável. Semanticamente diferente de undefined: void diz "não use o retorno", enquanto undefined diz "o valor é literalmente undefined".
// void em retorno de função
function log(msg: string): void {
console.log(msg);
// return; — OK
// return undefined; — OK
// return 42; — Erro!
}
// Diferença sutil: callback contextual void vs undefined
type VoidFn = () => void;
type UndefinedFn = () => undefined;
const arr = [1, 2, 3];
arr.forEach(x => x * 2); // OK — forEach espera () => void
// o retorno (number) é ignorado
// Com undefined, o retorno não pode ser ignorado
const fn: UndefinedFn = () => 42; // Erro!
A sutileza: uma função tipada como () => void
pode retornar qualquer valor — o que importa é que
o chamador não pode utilizá-lo. Isso permite usar
funções como Array.push onde um callback void é esperado.
Undefined e Null
Dois "nenhum valor" com semânticas distintas. Com strictNullChecks: true (padrão em projetos novos), null e undefined são tipos distintos e não atribuíveis a outros tipos sem union explícita. Esse é um dos principais benefícios do TypeScript sobre JavaScript puro.
UNDEFINED
// Variável declarada sem valor
let x: number | undefined;
console.log(x); // undefined
// Parâmetro opcional implica | undefined
function f(a?: string) {
// a: string | undefined
}
NULL
// Ausência intencional
let ref: string | null = null;
ref = "ok"; // permitido
// ?? — nullish coalescing
const nome = ref ?? "anônimo";
Tipos de Objetos
Interface
Contrato estrutural, preferida para APIs públicas.
Interfaces definem a forma de objetos. Suportam extensão, merging de declarações e implementação por classes. No sistema de tipos estrutural do TypeScript, qualquer objeto que satisfaça a forma é compatível — sem herança explícita necessária.
// Definição básica
interface Usuario {
readonly id: number; // imutável após criação
nome: string;
email?: string; // opcional (string | undefined)
[chave: string]: unknown; // index signature
}
// Extensão — herança estrutural
interface Admin extends Usuario {
permissoes: string[];
}
// Declaration merging — adição retroativa de campos
interface Usuario {
ativo: boolean; // fundida com a interface original!
}
// Implementação por classe
class UsuarioImpl implements Usuario {
readonly id = 1;
nome = "Yuri";
ativo = true;
}
Prefira interface para tipos de objetos que outros
desenvolvedores irão estender ou implementar.
Use type para unions, intersections e aliases de tipos
primitivos.
Class
Tipo e valor ao mesmo tempo — gera código em runtime.
Uma class em TypeScript é simultaneamente um tipo (usado pelo compilador) e um valor (objeto/função que existe em runtime). Isso difere de interface e type, que são apagados na compilação.
// Modificadores de acesso (existem apenas em TS — não em JS puro)
class Servico {
public nome: string; // padrão — acessível em qualquer lugar
private url: string; // apenas dentro da classe
protected config: object; // classe + subclasses
readonly id: number; // imutável após constructor
// Shorthand de constructor
constructor(private baseUrl: string) {
this.nome = "ServicoHTTP";
this.url = baseUrl;
this.config = {};
this.id = Math.random();
}
// Método genérico com constraint
async get<T extends object>(path: string): Promise<T> {
const res = await fetch(this.url + path);
return res.json() as Promise<T>;
}
}
Enum
Conjunto nomeado de constantes — gera código em runtime.
Enums geram um objeto JavaScript em runtime. Existem três variações com trade-offs importantes: enum numérico, enum de string, e const enum (eliminado no build).
// Enum numérico — valores auto-incrementados
enum Direcao { Norte, Sul, Leste, Oeste }
// Compilado: {0:"Norte", Norte:0, 1:"Sul", Sul:1, ...}
// Permite lookup reverso: Direcao[0] === "Norte"
// Enum de string — sem lookup reverso, mais seguro
enum Status {
Ativo = "ATIVO",
Inativo = "INATIVO",
Pendente = "PENDENTE",
}
// const enum — inlined no build, zero runtime footprint
const enum Tecla { Enter = 13, Escape = 27, Space = 32 }
if (event.keyCode === Tecla.Enter) { /* compilado como: === 13 */ }
// Alternativa idiomática moderna (sem enum — mais tree-shakeable)
const HTTP = { Ok: 200, NotFound: 404, Error: 500 } as const;
type HttpCode = typeof HTTP[keyof typeof HTTP]; // 200 | 404 | 500
Array e Tuple
Coleções homogêneas vs heterogêneas com tamanho fixo
Array — homogêneo
let a: number[] = [1, 2];
let b: Array<string> = ["x"];
// Readonly — previne mutação
const ids: ReadonlyArray<number> = [1, 2];
ids.push(3); // Erro!
// Inferência de tipo
const mix = [1, "a", true];
// tipo: (number | string | boolean)[]
Tuple — posições fixas
// Tipo por posição
type Par = [string, number];
const p: Par = ["id", 1];
// Labels (TS 4.0+)
type Range = [min: number, max: number];
// Rest elements
type CSV = [string, ...number[]];
// ["header", 1, 2, 3, ...]
Tuple como retorno múltiplo (padrão React Hooks)
function useState<T>(init: T): [T, (v: T) => void] {
let val = init;
return [val, (v) => { val = v; }];
}
const [count, setCount] = useState(0); // destructuring por posição
// Tuple readonly
type Imutavel = readonly [number, string];
Object
Tipo genérico — na prática, use tipos estruturais explícitos
O tipo object representa qualquer valor não-primitivo. É mais restrito que any mas menos útil que um tipo estrutural explícito, pois não permite acesso a propriedades.
// object (minúsculo) — qualquer não-primitivo
let o: object = { x: 1 };
o.x; // Erro! object não expõe propriedades
// Record<K, V> — objeto com chaves e valores tipados
const cache: Record<string, number> = {};
cache["hits"] = 42; // OK
// Tipo de objeto inline — preferido
function render(config: { width: number; height: number }) { }
// {} vs object vs Object
let a: {} = 42; // OK — {} aceita tudo menos null/undefined
let b: object = 42; // Erro! number é primitivo
let c: Object = 42; // OK — wrapper, aceita primitivos
Top Types
any — escape hatch
Desabilita toda verificação. Contagioso.
any é o tipo mais permissivo — e o mais perigoso. Ele é atribuível a qualquer tipo e aceita qualquer operação, desligando completamente o type checker. Seu perigo está no contágio: operações em any retornam any.
// any se propaga silenciosamente
const a: any = fetch(url);
const b = a.json(); // b: any
const c = b.data; // c: any
const d: number = c; // OK para o compilador — perigoso!
// Quando any é aceitável
// 1. Migração gradual de JS para TS
// 2. Bindings de libs JS sem @types
// 3. Testes com mocks parciais (prefira Partial<T>)
Uma única variável any pode contaminar toda uma
cadeia de operações.
Use unknown + narrowing sempre que a origem dos dados não for conhecida em tempo de compilação.
unknown — top type seguro
Supertipo de todos os tipos. Exige narrowing.
unknown foi introduzido no TypeScript 3.0 como alternativa segura a any. Todo tipo é atribuível a unknown, mas unknown só é utilizável após narrowing. É o tipo correto para dados externos (JSON, APIs, catch clauses).
// unknown aceita qualquer coisa...
let u: unknown = JSON.parse(rawJson);
// ...mas bloqueia uso sem narrowing
u.toUpperCase(); // Erro! Object is of type 'unknown'
u[0]; // Erro!
u + 1; // Erro!
// Formas de narrowing
if (typeof u === "string") { u.toUpperCase(); } // OK
if (u instanceof Date) { u.getFullYear(); } // OK
if (Array.isArray(u)) { u.map(x => x); } // OK
// Type guard customizado
function isUsuario(v: unknown): v is Usuario {
return typeof v === "object" && v !== null && "id" in v;
}
if (isUsuario(u)) { u.id; } // OK — TS sabe que é Usuario
// catch clause — TS 4.0+
try { } catch (e) {
// e: unknown (não any desde TS 4.4 com useUnknownInCatchVariables)
if (e instanceof Error) console.log(e.message);
}
Bottom Types
Subtipo de tudo. Representa impossibilidade.
never é o tipo vazio — nenhum valor pertence a ele. O compilador o infere automaticamente em branches impossíveis. Sua utilidade principal é na verificação de exaustividade de union types.
// 1. Funções que nunca retornam
function falhar(msg: string): never {
throw new Error(msg);
}
function loopInfinito(): never {
while (true) { }
}
// 2. Interseção impossível
type Impossivel = string & number; // never
// 3. Exaustividade de discriminated unions
type Forma = { kind: "circulo"; r: number }
| { kind: "retangulo"; w: number; h: number };
function area(f: Forma): number {
switch (f.kind) {
case "circulo": return Math.PI * f.r ** 2;
case "retangulo": return f.w * f.h;
default:
const _exaustivo: never = f;
// Se Forma ganhar um novo membro e esse case não for
// adicionado, o compilador dará ERRO aqui. Isso é intencional!
return falhar(`Forma não tratada`);
}
}
// 4. Filtro condicional em tipos
type SemString<T> = T extends string ? never : T;
type Resultado = SemString<string | number | boolean>;
// number | boolean (string foi filtrada)
Top comments (0)