DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on

Tipos em Typescript

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.

Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

NULL

// Ausência intencional
let ref: string | null = null;
ref = "ok"; // permitido

// ?? — nullish coalescing
const nome = ref ?? "anônimo";
Enter fullscreen mode Exit fullscreen mode

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.

Enter fullscreen mode Exit fullscreen mode

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>;
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)[]
Enter fullscreen mode Exit fullscreen mode

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, ...]
Enter fullscreen mode Exit fullscreen mode

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];
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)