DevTools

Cheatsheet TypeScript

JavaScript com tipagem estática

Voltar às linguagens
TypeScript
94 cards encontrados
Categorias:
Versões:

Tipos Básicos


12 cards
Tipos primitivos
let nome: string = "Ana";
let idade: number = 30;
let ativo: boolean = true;
let nada: null = null;
let indef: undefined = undefined;

Os tipos primitivos são string, number e boolean, mais null e undefined. A anotação : tipo é opcional quando o valor permite inferência.

Union types
let id: string | number;
id = "abc";
id = 123;

function format(v: string | number): string {
    return String(v);
}

Um union type permite que um valor seja de vários tipos, separados por |. Dentro da função só podes usar operações comuns a todos; para operações específicas faz narrowing.

const assertion (as const)
const config = {
    host: "localhost",
    porta: 3000,
} as const;

// config.porta = 4000; // ERRO: readonly
type Portas = typeof config.porta; // 3000

O as const torna o valor profundamente imutável e converte os tipos em literais (3000 em vez de number). É útil para constantes e para criar tipos a partir de dados.

Arrays e tuplos
let nums: number[] = [1, 2, 3];
let textos: Array<string> = ["a", "b"];

let tuplo: [string, number] = ["Ana", 30];
tuplo[0]; // string

Arrays tipam-se com tipo[] ou Array<tipo>. Um tuplo é um array com tamanho e tipos fixos por posição, útil para pares de valores.

Literal types
let dir: "esquerda" | "direita";
dir = "esquerda";

type Status = "ativo" | "inativo";
let s: Status = "ativo";

Um literal type restringe a variável a valores exatos. Combinado com unions cria conjuntos fechados de opções, mais seguros que string. Ideal para estados e configurações.

typeof e instanceof
function area(v: number | Date) {
    if (typeof v === "number") {
        return v * v; // number
    }
    return v.getTime(); // Date
}

Os operadores typeof (para primitivos) e instanceof (para classes) fazem narrowing: dentro do if o TypeScript afunila o tipo automaticamente, permitindo operações específicas.

any, unknown, never, void
let x: any = "qualquer";   // sem verificação
let y: unknown = 42;       // seguro, exige check

function erro(): never {
    throw new Error("falha");
}
function log(): void { }   // sem retorno

O any desativa a verificação (evita-o). O unknown aceita qualquer valor mas obriga a verificar antes de usar. never é para funções que nunca retornam e void para as que não devolvem valor.

Optional e null
interface User {
    nome: string;
    email?: string; // opcional
}
let valor: string | null = null;
let def: string = valor ?? "padrão";

O ? marca uma propriedade opcional (pode ser undefined). O operador ?? (nullish coalescing) devolve o valor à direita se o da esquerda for null ou undefined.

Objetos tipados
let ponto: { x: number; y: number } = {
    x: 1,
    y: 2,
};

let user: { nome: string; idade?: number };

Podes tipar objetos diretamente com { campo: tipo }. Cada propriedade é obrigatória salvo se marcada com ?. Para formas reutilizadas, prefere uma interface ou type.

Inferência de tipo
let nome = "Ana";        // string
let nums = [1, 2, 3];    // number[]
const obj = { x: 1, y: 2 };

// nome = 10; // ERRO: não é number

O TypeScript infere o tipo a partir do valor inicial, por isso muitas anotações são dispensáveis. Variáveis const têm tipos mais específicos. Deixa o compilador inferir sempre que possível.

Type assertion (as)
let el = document.getElementById("app") as HTMLDivElement;
let n = ("123" as string).length;

let input = document.querySelector("input")!;

A type assertion com as diz ao compilador para tratar um valor como um tipo específico, sem verificação. Usa-a só quando sabes mais que o TypeScript. Não converte o valor em runtime.

bigint e symbol
let grande: bigint = 9007199254740991n;
let id: symbol = Symbol("id");

let chave: unique symbol = Symbol("chave");

O bigint representa inteiros arbitrariamente grandes (sufixo n) e o symbol cria identificadores únicos. unique symbol é um tipo único associado a uma constante.

Funções


10 cards
Função tipada
function somar(a: number, b: number): number {
    return a + b;
}

const dobro = (x: number): number => x * 2;

Tipa os parâmetros e o retorno de uma função. O retorno muitas vezes é inferido, mas anotá-lo documenta o contrato. Funções arrow seguem a mesma regra.

Tipo de função
type Callback = (data: string) => void;

function fetchDados(cb: Callback) {
    cb("resultado");
}
fetchDados((d) => console.log(d));

Podes definir um tipo de função com type, descrevendo parâmetros e retorno. É reutilizável em várias assinaturas e torna os callbacks mais legíveis e seguros.

Retorno void vs undefined
function log(msg: string): void {
    console.log(msg);
}

function achar(id: number): string | undefined {
    return undefined;
}

Um retorno void significa que a função não devolve nada útil. Já undefined como tipo de retorno indica que pode devolver esse valor explicitamente. São conceitos distintos.

Parâmetros opcionais e default
function saudar(nome: string, titulo?: string) {
    return titulo ? `${titulo} ${nome}` : nome;
}

function conectar(host: string = "localhost") { }

Um parâmetro opcional marca-se com ? e pode ser undefined. Um parâmetro com valor padrão usa = valor e já é opcional na chamada. Os opcionais devem vir depois dos obrigatórios.

Arrow function tipada
const multiplicar: (a: number, b: number) => number =
    (a, b) => a * b;

const itens = [1, 2, 3].map((n: number) => n * 2);

Em arrow functions, o tipo declara-se na variável com a sintaxe (params) => retorno. Os parâmetros da implementação ficam inferidos. Em .map() o tipo do item é inferido do array.

Função genérica simples
function identidade<T>(valor: T): T {
    return valor;
}
identidade<string>("olá");
identidade(42); // T inferido como number

Uma função genérica usa <T> para aceitar qualquer tipo mantendo a relação entre entrada e saída. O tipo pode ser explícito ou inferido. É a base da programação reutilizável.

Rest parameters
function soma(...nums: number[]): number {
    return nums.reduce((a, b) => a + b, 0);
}
soma(1, 2, 3, 4); // 10

O rest parameter ...nome: tipo[] aceita um número variável de argumentos, tratados como um array tipado. Deve ser o último parâmetro da função.

Callbacks tipados
type Handler = (evento: string, codigo: number) => void;

function registar(h: Handler) {
    h("click", 200);
}
registar((ev, cod) => console.log(ev, cod));

Ao passar callbacks, tipa-os para que os parâmetros fiquem inferidos no uso. Assim o compilador verifica os argumentos e o retorno, evitando erros em funções de ordem superior.

Function overloads
function parse(v: string): number;
function parse(v: number): string;
function parse(v: string | number) {
    return typeof v === "string"
        ? parseInt(v) : String(v);
}

Os overloads definem várias assinaturas para a mesma função. As primeiras linhas declaram os casos e a última é a implementação. O compilador escolhe a assinatura certa conforme o argumento.

this tipado
interface Elemento {
    nome: string;
    mostrar(this: Elemento): void;
}

function mostrar(this: Elemento) {
    console.log(this.nome);
}

O TypeScript permite tipar o this como um pseudo-parâmetro na primeira posição. Não conta como argumento real, mas ajuda o compilador a verificar o contexto de chamada.

Interfaces e Types


11 cards
Interface básica
interface Pessoa {
    nome: string;
    idade: number;
    email?: string; // opcional
}
const p: Pessoa = { nome: "Ana", idade: 30 };

Uma interface define a forma de um objeto: nomes e tipos das propriedades. O ? marca campos opcionais. É a forma mais comum de tipar objetos em TypeScript.

Index signature
interface Dict {
    [key: string]: number;
}
const notas: Dict = { math: 95, port: 88 };

type Mapa = Record<string, number>;

Uma index signature [key: string]: tipo descreve objetos com chaves dinâmicas. O utilitário Record<string, tipo> é a forma moderna e concisa de fazer o mesmo.

Declaration merging
interface Janela {
    titulo: string;
}
interface Janela {
    largura: number;
}
// Janela tem titulo E largura

O declaration merging junta automaticamente interfaces com o mesmo nome numa só. É útil para estender tipos de bibliotecas. Só funciona com interface, não com type.

type alias
type Ponto = {
    x: number;
    y: number;
};

type ID = string | number;

O type cria um alias para qualquer tipo, incluindo objetos, unions e primitivos. Ao contrário da interface, aceita unions e tipos compostos. Para objetos simples são quase equivalentes.

Readonly
interface Config {
    readonly host: string;
    readonly porta: number;
}
const cfg: Config = { host: "x", porta: 80 };
// cfg.host = "y"; // ERRO

O modificador readonly torna uma propriedade imutável após a criação. Tentar reatribuir causa erro de compilação. Para arrays usa readonly tipo[] ou ReadonlyArray.

Extends múltiplo
interface TemId { id: number; }
interface TemData { criadoEm: Date; }

interface Entidade extends TemId, TemData {
    nome: string;
}

Uma interface pode estender várias interfaces de uma vez, separadas por vírgulas. A interface resultante herda todos os campos. É uma forma limpa de compor contratos reutilizáveis.

interface vs type
// interface: extensível, merging
interface A { x: number; }
interface A { y: number; } // merge

// type: unions, mapeados
type B = string | number;

Usa interface para contratos de objetos públicos (permite declaration merging e extends) e type para unions, tuplos e tipos derivados. Ambos funcionam para objetos.

Interface para função
interface Comparador {
    (a: number, b: number): number;
}

const cmp: Comparador = (a, b) => a - b;

Uma interface pode descrever uma assinatura de função com (params): retorno. Os parâmetros da implementação ficam inferidos. É uma alternativa ao type de função.

Type derivado de objeto
const config = {
    host: "localhost",
    porta: 3000,
} as const;

type Config = typeof config;

O operador typeof em posição de tipo cria um tipo a partir de um valor existente. Combinado com as const, gera tipos literais precisos. Evita duplicar a definição.

Extends
interface Animal {
    nome: string;
}
interface Cao extends Animal {
    raca: string;
}
const c: Cao = { nome: "Rex", raca: "Labrador" };

O extends cria uma interface que herda os campos de outra e adiciona os seus. A interface filha inclui todas as propriedades da pai. É a forma de compor contratos.

Intersection types (&)
type Nome = { nome: string };
type Idade = { idade: number };

type Pessoa = Nome & Idade;
const p: Pessoa = { nome: "Ana", idade: 30 };

Um intersection type combina vários tipos com &, exigindo as propriedades de todos. É o oposto do union: em vez de "um ou outro", é "todos em conjunto".

Classes


10 cards
Classe com tipos
class Pessoa {
    nome: string;
    private idade: number;

    constructor(nome: string, idade: number) {
        this.nome = nome;
        this.idade = idade;
    }
}

Numa class, declara os campos com os seus tipos antes do construtor. O TypeScript verifica as atribuições em this. Todos os campos obrigatórios devem ser inicializados.

Implements
interface Serializable {
    toJSON(): string;
}

class User implements Serializable {
    toJSON() { return "{}"; }
}

O implements garante que uma classe cumpre o contrato de uma interface. Se faltar um método ou propriedade, o compilador avisa. Uma classe pode implementar várias interfaces.

override
class Base {
    metodo() { return "base"; }
}
class Filha extends Base {
    override metodo() { return "filha"; }
}

A palavra-chave override marca explicitamente que um método substitui o do pai. Com a opção noImplicitOverride no tsconfig, é obrigatória e evita erros de renomeação.

Modificadores de acesso
class Conta {
    public titular: string;
    private saldo: number = 0;
    protected id: number = 0;
    readonly banco: string = "CGD";
}

O public é visível em todo o lado, private só na classe, protected na classe e subclasses e readonly impede reatribuição. Por omissão os membros são public.

Abstract class
abstract class Forma {
    abstract area(): number;
    descricao() { return "forma"; }
}
// new Forma(); // ERRO

Uma classe abstract não pode ser instanciada diretamente e serve de base. Pode ter métodos abstratos (sem corpo, com abstract) que as subclasses são obrigadas a implementar.

Classe genérica
class Pilha<T> {
    private itens: T[] = [];
    push(item: T) { this.itens.push(item); }
    pop(): T | undefined { return this.itens.pop(); }
}
const p = new Pilha<number>();

Uma classe genérica usa <T> após o nome para parametrizar os tipos dos seus membros. O tipo é definido ao instanciar com new Classe<tipo>() ou inferido.

Parameter properties
class Ponto {
    constructor(
        public x: number,
        public y: number
    ) {}
}
const p = new Ponto(1, 2);

Um parameter property declara e atribui um campo diretamente no construtor, juntando um modificador (public, private, readonly) ao parâmetro. Reduz muito o código boilerplate.

Getters e setters
class Circulo {
    constructor(private raio: number) {}
    get area(): number {
        return Math.PI * this.raio ** 2;
    }
    set novoRaio(r: number) { this.raio = r; }
}

Os getters (get) e setters (set) definem acesso controlado a uma propriedade. Acedem-se como campos normais, mas permitem validação ou cálculo por trás.

Herança (extends)
class Animal {
    constructor(public nome: string) {}
    falar() { return "..."; }
}
class Cao extends Animal {
    falar() { return "Au!"; }
}

A herança com extends permite que uma classe filha reutilize e substitua métodos do pai. O constructor da filha deve chamar super() se o pai tiver um.

Static
class MathUtils {
    static readonly PI = 3.14159;
    static dobro(n: number): number {
        return n * 2;
    }
}
MathUtils.dobro(5);

Membros static pertencem à classe e não à instância, acedidos com Classe.membro. Podem ser métodos, campos ou readonly. Úteis para utilitários e constantes.

Generics


10 cards
Função genérica
function primeiro<T>(arr: T[]): T | undefined {
    return arr[0];
}
primeiro([1, 2, 3]);    // number
primeiro(["a", "b"]);   // string

Uma função genérica declara <T> antes dos parâmetros. O tipo é inferido a partir dos argumentos e mantém a relação entre entrada e saída, sem recorrer a any.

Genéricos com default
interface Resposta<T = unknown> {
    dados: T;
    erro?: string;
}
const r: Resposta<string[]> = { dados: [] };
const s: Resposta = { dados: null };

Um parâmetro genérico pode ter um valor padrão com T = tipo. Se não for indicado, usa o default. Útil para tornar tipos opcionais sem perder segurança.

Constraint com keyof
function ordenar<T, K extends keyof T>(
    itens: T[], chave: K
): T[] {
    return [...itens].sort(
        (a, b) => (a[chave] > b[chave] ? 1 : -1)
    );
}

Combinar K extends keyof T com arrays permite funções seguras que operam sobre uma propriedade específica. O compilador verifica que a chave existe em T.

Interface genérica
interface Caixa<T> {
    valor: T;
    abrir(): T;
}
const c: Caixa<number> = {
    valor: 42,
    abrir: () => 42,
};

Uma interface genérica usa <T> para criar tipos reutilizáveis. O parâmetro é fornecido ao usar (Caixa<number>). Muito comum em respostas de API e contentores.

Múltiplos parâmetros
function juntar<A, B>(a: A, b: B): A & B {
    return { ...a, ...b };
}
const r = juntar({ x: 1 }, { y: 2 });
// r: { x: number } & { y: number }

Uma função pode ter vários parâmetros de tipo, separados por vírgulas. Cada um é inferido independentemente. O retorno A & B combina as formas dos dois objetos.

Genéricos em Promises
async function fetchUser(id: number): Promise<User> {
    const r = await fetch(`/api/${id}`);
    return r.json();
}
const u = await fetchUser(1); // User

O Promise<T> é genérico: indica o tipo do valor resolvido. Ao anotar o retorno de funções async, o await devolve o tipo correto. Essencial para código assíncrono type-safe.

Constraints (extends)
function maior<T extends { length: number }>(
    a: T, b: T
): T {
    return a.length > b.length ? a : b;
}

Uma constraint T extends Tipo restringe o genérico a tipos que tenham certa forma. Aqui garante que T tem length, permitindo aceder a essa propriedade em segurança.

Indexed access (T[K])
interface User {
    nome: string;
    idade: number;
}
type Nome = User["nome"];   // string
type Valor = User[keyof User]; // string | number

O indexed access Tipo["campo"] extrai o tipo de uma propriedade. Com Tipo[keyof Tipo] obténs o union de todos os valores. É uma forma de derivar tipos sem duplicar.

keyof
function get<T, K extends keyof T>(
    obj: T, key: K
): T[K] {
    return obj[key];
}
get({ nome: "Ana" }, "nome"); // string

O keyof T produz um union das chaves de T. Combinado com K extends keyof T, garante que a chave existe. O tipo de retorno T[K] é o tipo dessa propriedade.

Arrow genérica em .tsx
// em .ts
const id = <T>(x: T): T => x;

// em .tsx (React) usa trailing comma
const id2 = <T,>(x: T): T => x;

Em ficheiros .tsx, a seta genérica <T> confunde-se com JSX. Adiciona uma vírgula <T,> ou usa <T extends unknown> para o parser reconhecer o genérico.

Utility Types


11 cards
Partial / Required
type Parcial = Partial<Pessoa>;
// todos os campos opcionais

type Completo = Required<Parcial>;
// todos obrigatórios

O Partial<T> torna todas as propriedades opcionais, útil para funções de atualização. O Required<T> faz o inverso, removendo os ? e tornando tudo obrigatório.

ReturnType
function somar(a: number, b: number) {
    return a + b;
}
type R = ReturnType<typeof somar>; // number

O ReturnType<T> extrai o tipo de retorno de uma função. Usa-se com typeof funcao para obter o tipo da função. Evita duplicar o tipo de retorno manualmente.

Awaited
type A = Awaited<Promise<string>>;   // string
type B = Awaited<Promise<number[]>>; // number[]
type C = Awaited<boolean>;           // boolean

O Awaited<T> "desembrulha" o tipo resolvido de uma Promise, tal como o operador await. Funciona recursivamente em promises aninhadas. Útil para tipar resultados assíncronos.

Pick / Omit
type Nome = Pick<Pessoa, "nome">;
type SemEmail = Omit<Pessoa, "email">;

type Basico = Pick<User, "id" | "nome">;

O Pick<T, K> seleciona apenas as propriedades indicadas e o Omit<T, K> exclui-as, mantendo o resto. São ideais para criar variantes de um tipo sem o reescrever.

Extract / Exclude
type T = "a" | "b" | "c";
type AB = Extract<T, "a" | "b">; // "a" | "b"
type C = Exclude<T, "a" | "b">;  // "c"

O Extract<T, U> mantém de um union só os tipos atribuíveis a U; o Exclude<T, U> remove-os. São úteis para filtrar membros de unions de literais.

InstanceType
class User {
    nome = "Ana";
}
type U = InstanceType<typeof User>; // User

type Ctor = new () => User;

O InstanceType<T> extrai o tipo da instância de um construtor. Usa-se com typeof Classe. É comum em fábricas genéricas que recebem construtores e devolvem instâncias.

Record
type Notas = Record<string, number>;
const n: Notas = { math: 95, port: 88 };

type Mapa = Record<"a" | "b", boolean>;

O Record<K, V> cria um objeto cujas chaves são do tipo K e valores do tipo V. Mais preciso que uma index signature quando as chaves são conhecidas.

NonNullable
type T = string | null | undefined;
type NN = NonNullable<T>; // string

function usar(v: string | undefined) {
    const s: NonNullable<typeof v> = v ?? "";
}

O NonNullable<T> remove null e undefined de um tipo. É útil para garantir que um valor está presente após uma verificação, sem reescrever o tipo.

Uppercase / Capitalize
type A = Uppercase<"ana">;    // "ANA"
type B = Lowercase<"ANA">;    // "ana"
type C = Capitalize<"ana">;   // "Ana"
type D = Uncapitalize<"Ana">; // "ana"

Os utility types de template literal transformam strings a nível de tipo: Uppercase, Lowercase, Capitalize e Uncapitalize. Só operam em tipos literais de string.

Readonly<T>
type Imutavel = Readonly<Pessoa>;

const p: Imutavel = { nome: "Ana", idade: 30 };
// p.idade = 31; // ERRO: readonly

O Readonly<T> torna todas as propriedades de T imutáveis. Qualquer reatribuição causa erro. É superficial (não afeta objetos aninhados); para arrays usa ReadonlyArray.

Parameters
function somar(a: number, b: number) {
    return a + b;
}
type P = Parameters<typeof somar>;
// [a: number, b: number]

O Parameters<T> extrai os tipos dos parâmetros de uma função como um tuplo. Usa-se com typeof. Prático para reutilizar a assinatura de funções existentes.

Tipos Avançados


11 cards
Type guards
function processar(v: string | number) {
    if (typeof v === "string") {
        return v.toUpperCase();
    }
    return v.toFixed(2);
}

Os type guards usam typeof, instanceof ou verificações para afunilar (narrowing) um union. Dentro de cada ramo o TypeScript sabe o tipo exato.

Template literal types
type Evento = `on${Capitalize<"click" | "focus">}`;
// "onClick" | "onFocus"

type Rota = `/${string}`;

Os template literal types criam tipos string a partir de template literals. Combinados com unions geram todas as combinações. Úteis para nomes de eventos, rotas e padrões.

Exhaustiveness (never)
function area(f: Forma): number {
    switch (f.tipo) {
        case "circulo": return Math.PI * f.raio ** 2;
        case "quadrado": return f.lado ** 2;
        default:
            const _ex: never = f;
            return _ex;
    }
}

Atribuir o caso default a uma variável never garante exaustão: se adicionares um novo caso ao union sem tratar, o compilador avisa. Torna o switch à prova de futuro.

Discriminated unions
type Forma =
    | { tipo: "circulo"; raio: number }
    | { tipo: "quadrado"; lado: number };

function area(f: Forma) {
    switch (f.tipo) {
        case "circulo": return Math.PI * f.raio ** 2;
        case "quadrado": return f.lado ** 2;
    }
}

Uma discriminated union é um union de objetos com um campo comum literal (o discriminador, aqui tipo). Um switch sobre ele faz narrowing automático e seguro.

infer
type Unpack<T> = T extends Promise<infer U>
    ? U : T;

type R = Unpack<Promise<string>>; // string

A palavra-chave infer declara uma variável de tipo dentro de um conditional, deixando o TypeScript deduzi-la. Aqui extrai o tipo interno de uma Promise.

Recursive types
type Json =
    | string
    | number
    | boolean
    | null
    | Json[]
    | { [key: string]: Json };

Um recursive type referencia-se a si próprio, descrevendo estruturas aninhadas como JSON ou árvores. O TypeScript resolve a recursão automaticamente. Útil para dados hierárquicos.

Mapped types
type Opcional<T> = {
    [K in keyof T]?: T[K];
};

type Nullable<T> = {
    [K in keyof T]: T[K] | null;
};

Um mapped type cria um novo tipo percorrendo as chaves de outro com [K in keyof T]. Permite transformar todas as propriedades programaticamente. É a base dos utility types.

Narrowing com in
type Cao = { latir(): void };
type Gato = { miar(): void };

function falar(a: Cao | Gato) {
    if ("latir" in a) a.latir();
    else a.miar();
}

O operador in verifica se uma propriedade existe no objeto e faz narrowing do union. É uma alternativa ao typeof quando os tipos se distinguem por campos presentes.

Mapped com modificadores
type SoLeitura<T> = {
    +readonly [K in keyof T]: T[K];
};
type SemOpcionais<T> = {
    [K in keyof T]-?: T[K];
};

Num mapped type, os prefixos + e - adicionam ou removem modificadores. +readonly torna imutável e -? remove a opcionalidade. Dão controlo fino sobre a transformação.

Conditional types
type IsString<T> = T extends string
    ? "sim" : "não";

type A = IsString<string>; // "sim"
type B = IsString<number>; // "não"

Um conditional type escolhe entre dois tipos com a sintaxe T extends U ? X : Y. É o "if" do sistema de tipos. Combina com infer para extrair tipos.

Type predicates (is)
function isString(v: unknown): v is string {
    return typeof v === "string";
}

if (isString(valor)) {
    valor.toUpperCase(); // string
}

Um type predicate param is Tipo no retorno cria um guard personalizado. Quando a função devolve true, o TypeScript afunila o tipo no ramo. Ideal para verificações reutilizáveis.

Módulos e Enums


9 cards
Export / Import
// modulo.ts
export const PI = 3.14;
export function somar(a: number, b: number) { }
export default class App { }

// uso.ts
import App, { PI, somar } from "./modulo";

O export disponibiliza valores e o import traz-os de outro módulo. O export default define a exportação principal, importada sem chavetas. Os nomeados usam { }.

Enum numérico vs string
enum Num { A, B, C } // 0, 1, 2
enum Str { A = "a", B = "b" }

let n: Num = Num.A; // 0
let s: Str = Str.A; // "a"

Enums numéricos auto-incrementam a partir de 0 e permitem mapeamento inverso. Enums string são mais legíveis em logs e debugging. Os de string são geralmente mais seguros.

Re-export e barrel
// index.ts (barrel)
export { User } from "./user";
export { Post } from "./post";
export * from "./utils";

import { User, Post } from "./index";

Um ficheiro barrel (geralmente index.ts) re-exporta vários módulos com export { } ou export *. Simplifica os imports, permitindo importar tudo de um só local.

Type-only import/export
import type { User } from "./tipos";
export type { Config } from "./config";

import { type Options, criar } from "./lib";

O import type importa apenas tipos, que são removidos na compilação. Evita imports desnecessários em runtime e conflitos. Com verbatimModuleSyntax no tsconfig torna-se obrigatório.

Namespace
namespace Utils {
    export function format(s: string) {
        return s.trim();
    }
}
Utils.format("olá");

Um namespace agrupa código relacionado sob um nome. É uma forma antiga de organização; hoje os módulos ES (import/export) são preferidos. Ainda útil em código legado ou global.

Enums
enum Direcao {
    Cima = "UP",
    Baixo = "DOWN",
}
let d: Direcao = Direcao.Cima;

Um enum define um conjunto de constantes nomeadas. Pode ter valores string ou numéricos. Acede-se com Enum.Membro. Em código moderno, unions de literais são muitas vezes preferidos.

Declaration files (.d.ts)
// global.d.ts
declare const API_URL: string;

interface Window {
    minhaLib: string;
}

Ficheiros .d.ts contêm apenas declarações de tipos, sem código executável. Servem para tipar bibliotecas sem tipos ou variáveis globais. O declare anuncia existência sem implementar.

Const enum
const enum Status {
    Ativo = 1,
    Inativo = 0,
}
let s = Status.Ativo; // inlinado como 1

Um const enum é totalmente inlinado em tempo de compilação, sem gerar objeto em runtime. É mais eficiente, mas não pode ser inspecionado dinamicamente. Usa com moderação.

declare module (wildcard)
declare module "*.css" {
    const classes: Record<string, string>;
    export default classes;
}

declare module "lib-sem-tipos";

O declare module com padrão (ex.: "*.css") tipa imports não-TS, como CSS ou imagens. Também declara módulos sem tipos para evitar erros. Coloca-se num .d.ts.

Dicas e Configuração


10 cards
tsconfig.json essencial
{
    "compilerOptions": {
        "strict": true,
        "target": "ES2022",
        "module": "ESNext",
        "outDir": "./dist",
        "esModuleInterop": true
    }
}

O tsconfig.json configura o compilador. O strict: true ativa todas as verificações rigorosas. target define a versão do JS gerado e outDir o destino. Cria com tsc --init.

strict e flags úteis
{
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "noImplicitOverride": true
}

O strict ativa strictNullChecks, noImplicitAny e outros. Flags como noUnusedLocals e noImplicitOverride reforçam a qualidade. Ativa o máximo que o projeto permitir.

Erros comuns
// usar == em vez de ===
// esquecer await numa Promise
// any em vez de unknown
// mutar um readonly
// comparar objeto com ===

Erros frequentes: usar any em vez de unknown, esquecer await, comparar objetos com === (compara referência) e ignorar readonly. O strict apanha a maioria.

CLI: comandos
npx tsc --init      // cria tsconfig
npx tsc --watch     // recompila ao guardar
npx tsc --noEmit    // só verifica tipos
npx tsc app.ts      // compila um ficheiro

O compilador tsc compila e verifica tipos. --watch recompila automaticamente e --noEmit só valida sem gerar ficheiros, útil em CI. Instala com npm i -D typescript.

Optional chaining (?.)
const cidade = user?.endereco?.cidade;
const metodo = obj.metodo?.();
const item = lista?.[0];

O optional chaining ?. acede a propriedades aninhadas com segurança: se algum valor for null ou undefined, devolve undefined em vez de lançar erro. Funciona em métodos e índices.

Boas práticas
// evitar any (usar unknown)
// ativar strict: true
// interfaces para objetos
// type para unions/aliases
// narrowing em vez de as

Evita any (prefere unknown), ativa strict, usa interface para objetos e type para unions. Prefere narrowing a as e deixa o compilador inferir sempre que possível.

Non-null assertion (!)
let el = document.getElementById("app")!;
el.textContent = "Olá";

// diz ao TS: "não é null/undefined"

O operador ! (non-null assertion) garante ao compilador que um valor não é null nem undefined. Não faz verificação em runtime; usa-o só quando tens a certeza.

Definite assignment (!)
class User {
    nome!: string; // será atribuído depois

    constructor() {
        this.init();
    }
    init() { this.nome = "Ana"; }
}

O ! após o nome de um campo (definite assignment) diz ao TypeScript que a propriedade será inicializada, mesmo que não no construtor. Evita o erro de "não inicializado". Usa com cuidado.

satisfies (TS 4.9+)
const config = {
    host: "localhost",
    porta: 3000,
} satisfies Record<string, string | number>;

config.porta.toFixed(); // ainda inferido

O operador satisfies valida que um valor cumpre um tipo sem perder a inferência específica. Ao contrário de : tipo, mantém os tipos exatos das propriedades. Combina segurança e precisão.

@ts-expect-error
// @ts-expect-error: valor inválido propositado
const n: number = "texto";

// @ts-ignore: suprime o próximo erro
const x: string = 123;

O comentário @ts-expect-error suprime um erro esperado e avisa se ele deixar de existir (melhor que @ts-ignore). Usa-os pontualmente e com justificação, nunca em massa.