Dominando Arrays, Tuplas e Enums em TypeScript na Prática em Projetos Reais Já leu

Arrays em TypeScript: Fundamentos e Aplicações Práticas Arrays são um dos tipos de dados mais fundamentais na programação. Em TypeScript, eles vão além da sintaxe simples do JavaScript porque permitem definir o tipo exato de elementos que você armazenará, oferecendo segurança de tipo e melhor intellisense na IDE. Um array em TypeScript é uma coleção ordenada de elementos do mesmo tipo. Você pode declará-lo de duas formas principais: usando a notação com colchetes ( ) ou usando o genérico . A diferença é apenas sintática; ambas são equivalentes. Os métodos de array como , e são essenciais no dia a dia. Quando usados com tipagem correta, eles previnem bugs silenciosos. Observe como o TypeScript verifica os tipos nos callbacks: Idade média: ${idadeTotal / usuarios.length} Arrays Multidimensionais Às vezes você precisa de estruturas mais complexas, como matrizes. A sintaxe é simples, mas a tipagem deve ser clara para evitar confusões: --- Tuplas: Quando a Ordem e o Tipo Importam Tuplas são

Arrays em TypeScript: Fundamentos e Aplicações Práticas

Arrays são um dos tipos de dados mais fundamentais na programação. Em TypeScript, eles vão além da sintaxe simples do JavaScript porque permitem definir o tipo exato de elementos que você armazenará, oferecendo segurança de tipo e melhor intellisense na IDE.

Um array em TypeScript é uma coleção ordenada de elementos do mesmo tipo. Você pode declará-lo de duas formas principais: usando a notação com colchetes (tipo[]) ou usando o genérico Array<tipo>. A diferença é apenas sintática; ambas são equivalentes.

// Duas formas de declarar arrays - ambas válidas
const numeros: number[] = [1, 2, 3, 4, 5];
const nomes: Array<string> = ["Alice", "Bob", "Carlos"];

// TypeScript infere o tipo automaticamente
const mistos = [1, "dois", true]; // any[] (evite isso)

// Solução melhor: seja explícito
const dados: (number | string)[] = [1, "dois", 3, "quatro"];

Os métodos de array como map, filter e reduce são essenciais no dia a dia. Quando usados com tipagem correta, eles previnem bugs silenciosos. Observe como o TypeScript verifica os tipos nos callbacks:

interface Usuario {
  id: number;
  nome: string;
  idade: number;
}

const usuarios: Usuario[] = [
  { id: 1, nome: "Ana", idade: 28 },
  { id: 2, nome: "Bruno", idade: 35 },
  { id: 3, nome: "Camila", idade: 22 }
];

// map com tipagem correta
const nomesMaiusculos = usuarios.map(usuario => usuario.nome.toUpperCase());
// Resultado: ["ANA", "BRUNO", "CAMILA"]

// filter com lógica de negócio
const maioresDeIdade = usuarios.filter(usuario => usuario.idade >= 18);

// reduce para agregação
const idadeTotal = usuarios.reduce((soma, usuario) => soma + usuario.idade, 0);
console.log(`Idade média: ${idadeTotal / usuarios.length}`); // 28.33

Arrays Multidimensionais

Às vezes você precisa de estruturas mais complexas, como matrizes. A sintaxe é simples, mas a tipagem deve ser clara para evitar confusões:

// Matriz 2D simples
const matriz: number[][] = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

// Acessando elementos
console.log(matriz[1][2]); // 6

// Array de objetos aninhados
interface Produto {
  id: number;
  nome: string;
  preços: number[];
}

const catalogo: Produto[] = [
  { id: 1, nome: "Notebook", preços: [2500, 2400, 2300] },
  { id: 2, nome: "Mouse", preços: [50, 45, 40] }
];

// Acessar preço histórico
const ultimoPreço = catalogo[0].preços[catalogo[0].preços.length - 1];

Tuplas: Quando a Ordem e o Tipo Importam

Tuplas são arrays com comprimento fixo e tipos específicos para cada posição. Elas são perfeitas quando você retorna múltiplos valores de uma função ou precisa garantir uma estrutura rígida.

A diferença fundamental entre um array e uma tupla é que a tupla não permite adicionar ou remover elementos além do que foi definido. Isso torna o código mais seguro e autodocumentado, pois qualquer pessoa lendo o tipo sabe exatamente quantas e quais informações esperar.

// Tupla básica: [nome, idade]
type Pessoa = [string, number];

const joao: Pessoa = ["João", 30];
const maria: Pessoa = ["Maria", 28];

// Erro de compilação - TypeScript previne
// const invalido: Pessoa = ["Pedro"]; // Falta um elemento
// const invalido: Pessoa = ["Pedro", 25, "extra"]; // Elemento extra

// Usando tuplas em funções
function criarUsuario(nome: string, email: string): [string, string, boolean] {
  return [nome, email, true];
}

const [nomeUsuario, emailUsuario, ativo] = criarUsuario("Alice", "alice@email.com");
console.log(`${nomeUsuario} - ${emailUsuario} - Ativo: ${ativo}`);

Tuplas Etiquetadas e Opcionais

TypeScript permite etiquetar cada posição da tupla, tornando o código muito mais legível. Você também pode marcar elementos como opcionais com o operador ?:

// Tupla etiquetada - nomes descritivos para cada posição
type APIResponse = [status: number, dados: string, timestamp: Date];

const resposta: APIResponse = [200, '{"id": 1}', new Date()];
console.log(`Status: ${resposta[0]}`); // Status: 200

// Elementos opcionais - útil para retornos variáveis
type ResultadoOperacao = [sucesso: boolean, mensagem?: string, erro?: Error];

const sucesso: ResultadoOperacao = [true];
const falha: ResultadoOperacao = [false, "Operação falhou", new Error("Banco de dados indisponível")];

// Tupla com comprimento mínimo
type ListaComResto = [primeiro: string, segundo: string, ...resto: number[]];

const exemplo: ListaComResto = ["a", "b", 1, 2, 3, 4];

Tuplas em Casos Reais

Tuplas brilham quando você precisa retornar múltiplos valores correlacionados. Veja um exemplo prático de uma função que processa um upload:

interface ArquivoUpload {
  nome: string;
  tamanho: number;
  tipo: string;
}

function processarUpload(arquivo: ArquivoUpload): 
  [sucesso: boolean, caminhoOuErro: string, tamanhoEmMB?: number] 
{
  if (arquivo.tamanho > 10 * 1024 * 1024) {
    return [false, "Arquivo excede 10MB"];
  }

  if (!["image/jpeg", "image/png"].includes(arquivo.tipo)) {
    return [false, "Tipo de arquivo não permitido"];
  }

  const tamanhoEmMB = arquivo.tamanho / (1024 * 1024);
  return [true, `/uploads/${arquivo.nome}`, tamanhoEmMB];
}

// Usando a tupla retornada
const [ok, info, mb] = processarUpload({
  nome: "foto.jpg",
  tamanho: 5 * 1024 * 1024,
  tipo: "image/jpeg"
});

if (ok) {
  console.log(`Upload bem-sucedido: ${info} (${mb?.toFixed(2)}MB)`);
} else {
  console.log(`Erro: ${info}`);
}

Enums: Valores Nomeados e Segurança de Tipo

Enums (enumerações) permitem definir um conjunto de constantes nomeadas. Em vez de usar strings ou números mágicos espalhados pelo código, você define uma enum uma vez e a reutiliza. Isso torna o código mais seguro, mantível e legível.

TypeScript oferece dois tipos de enums: numéricos e de string. Cada um tem seus casos de uso ideais. Enums numéricos são compilados para constantes simples, enquanto enums de string são mais verbosos mas oferecem melhor legibilidade.

// Enum numérico - valores são atribuídos automaticamente (0, 1, 2...)
enum Status {
  Pendente,      // 0
  Aprovado,      // 1
  Rejeitado      // 2
}

let statusPedido: Status = Status.Pendente;
console.log(statusPedido); // 0

// Enum de string - mais legível e explícito
enum Role {
  Admin = "ADMIN",
  Editor = "EDITOR",
  Viewer = "VIEWER"
}

let permissaUsuario: Role = Role.Editor;
console.log(permissaUsuario); // "EDITOR"

// Verificar tipo
if (permissaUsuario === Role.Editor) {
  console.log("Acesso de editor concedido");
}

Enums na Prática: Aplicações em Código Real

Enums são indispensáveis quando você precisa de um conjunto fechado de opções. Evitam strings espalhadas pela codebase e facilitam refatoração:

enum TipoPagamento {
  Credito = "CREDITO",
  Debito = "DEBITO",
  Pix = "PIX",
  Boleto = "BOLETO"
}

enum StatusPedido {
  Cancelado = "CANCELADO",
  AguardandoPagamento = "AGUARDANDO_PAGAMENTO",
  Processando = "PROCESSANDO",
  Entregue = "ENTREGUE"
}

interface Pedido {
  id: number;
  tipoPagamento: TipoPagamento;
  status: StatusPedido;
  valor: number;
}

function processarPagamento(pedido: Pedido): boolean {
  switch (pedido.tipoPagamento) {
    case TipoPagamento.Credito:
      console.log("Processando cartão de crédito...");
      return true;

    case TipoPagamento.Pix:
      console.log("Gerando QR Code PIX...");
      return true;

    case TipoPagamento.Boleto:
      console.log("Emitindo boleto...");
      return true;

    case TipoPagamento.Debito:
      console.log("Processando débito...");
      return true;

    default:
      // TypeScript garante que todos os casos foram cobertos
      const _nunca: never = pedido.tipoPagamento;
      return false;
  }
}

// Usando a enum
const meuPedido: Pedido = {
  id: 123,
  tipoPagamento: TipoPagamento.Pix,
  status: StatusPedido.AguardandoPagamento,
  valor: 199.90
};

processarPagamento(meuPedido);

Enums Heterogêneos e Reverse Mapping

TypeScript permite enums com valores mistos (números e strings), embora isso não seja recomendado. Mais útil é conhecer o reverse mapping automático em enums numéricos:

// Reverse mapping - acesso bidirecional
enum Direcao {
  Cima = 0,
  Direita = 1,
  Baixo = 2,
  Esquerda = 3
}

console.log(Direcao.Cima);        // 0
console.log(Direcao[0]);          // "Cima"
console.log(Direcao[Direcao.Cima]); // "Cima"

// Útil para debug ou quando você recebe um valor numérico da API
function obterDescricaoDirecao(dir: number): string {
  return Direcao[dir]; // Retorna o nome da direção
}

console.log(obterDescricaoDirecao(2)); // "Baixo"

Combinando Arrays, Tuplas e Enums: Um Projeto Completo

Na prática, esses três conceitos trabalham juntos. Veja um exemplo realista de um sistema de carrinhos de compra:

// Definir tipos com enum e tupla
enum TamanhoUm {
  PP = "PP",
  P = "P",
  M = "M",
  G = "G",
  GG = "GG"
}

enum StatusCarrinho {
  Vazio = "VAZIO",
  Preenchido = "PREENCHIDO",
  Finalizado = "FINALIZADO"
}

type ItemCarrinho = [id: number, quantidade: number, preco: number];

interface Produto {
  id: number;
  nome: string;
  tamanho: TamanhoUm;
}

interface Carrinho {
  usuario_id: number;
  status: StatusCarrinho;
  itens: ItemCarrinho[];
  dataCriacao: Date;
}

// Funções que usam tudo junto
function adicionarAoCarrinho(
  carrinho: Carrinho,
  produto: Produto,
  quantidade: number,
  preco: number
): Carrinho {
  const novoItem: ItemCarrinho = [produto.id, quantidade, preco];

  return {
    ...carrinho,
    itens: [...carrinho.itens, novoItem],
    status: StatusCarrinho.Preenchido
  };
}

function calcularTotal(carrinho: Carrinho): number {
  return carrinho.itens.reduce((total, [_, quantidade, preco]) => {
    return total + (quantidade * preco);
  }, 0);
}

function obterResumoCarrinho(carrinho: Carrinho): [status: string, total: number, quantidade: number] {
  const total = calcularTotal(carrinho);
  const quantidade = carrinho.itens.reduce((acc, [_, qtd]) => acc + qtd, 0);

  return [StatusCarrinho[carrinho.status], total, quantidade];
}

// Usando tudo junto
const meuCarrinho: Carrinho = {
  usuario_id: 1,
  status: StatusCarrinho.Vazio,
  itens: [],
  dataCriacao: new Date()
};

const produto1: Produto = { id: 1, nome: "Camiseta", tamanho: TamanhoUm.M };
const carrinhoAtualizado = adicionarAoCarrinho(meuCarrinho, produto1, 2, 89.90);

const [status, total, quantidade] = obterResumoCarrinho(carrinhoAtualizado);
console.log(`Status: ${status}, Total: R$ ${total.toFixed(2)}, Itens: ${quantidade}`);
// Saída: Status: PREENCHIDO, Total: R$ 179.80, Itens: 2

Este exemplo mostra como enums fornecem valores seguros, tuplas garantem estrutura de dados e arrays armazenam coleções de forma tipada e eficiente.


Conclusão

Você aprendeu três pilares fundamentais do TypeScript: Arrays oferecem coleções flexíveis e tipadas com métodos poderosos; Tuplas garantem estruturas rígidas quando você retorna múltiplos valores ou precisa de uma forma específica; e Enums eliminam valores mágicos (strings e números soltos) fornecendo constantes nomeadas e seguras.

O verdadeiro poder está em combinar esses três conceitos. Use arrays para coleções de tamanho variável, tuplas para retornos de múltiplos valores com tipos específicos por posição, e enums para representar estados e valores finitos. Essa combinação não apenas torna seu código mais seguro (TypeScript detectará erros em tempo de compilação), mas também torna muito mais fácil para outros desenvolvedores entender sua intenção.


Referências


Artigos relacionados