Javascript vanilla

Esta página cobre o encontro do JavaScript com a marcação: como achar um elemento, como escutar um clique e como falar com a API. As convenções da própria linguagem estão na documentação completa de JavaScript.

Vale como referência para o projeto que roda direto no navegador, sem bundler (empacotador) nem framework. O que era feito com jQuery hoje tem equivalente nativo, e sem dependência.

Conceitos fundamentais

ConceitoO que é
DOM (Document Object Model · Modelo de Objeto do Documento)A árvore de objetos que o navegador monta em memória a partir do HTML. É nela que o JavaScript mexe
type module (script como módulo)<script type="module">, que liga import e export, o modo estrito e o defer automático
event delegation (delegação de eventos)Escutar o evento no elemento pai e descobrir pelo event.target qual filho recebeu o clique
querySelector (seleção por seletor CSS)Acha o elemento com a mesma sintaxe do CSS. Cobre o que getElementById e o jQuery faziam
classList (lista de classes)Adiciona, remove e alterna a classe de um elemento sem mexer no atributo como texto
AbortController (controlador de cancelamento)Um controller.abort() derruba os listeners registrados e cancela o fetch que ainda está em voo

O script como módulo já vem com defer

Escrever type="module" na tag resolve três coisas de uma vez: o script passa a aceitar import e export, roda em modo estrito e espera a leitura do documento terminar, como se tivesse defer.

<head>
  <script src="/js/app.js" type="module"></script>
</head>

Dentro de um módulo, o código não precisa esperar por DOMContentLoaded. Quando ele roda, a página já está montada.

Guarde o elemento numa variável antes de reusá-lo

Cada querySelector percorre a árvore da página de novo. Chamar o mesmo seletor duas vezes seguidas faz o navegador procurar duas vezes o elemento que ele acabou de achar.

Guardar a busca numa variável resolve a repetição, e ainda dá um nome ao elemento, o que diz ao leitor o que aquele nó representa.

❌ Ruim: o mesmo seletor percorre a árvore duas vezes
document.querySelector(".card__title").classList.add("active");
document.querySelector(".card__title").textContent = "Updated";
✅ Bom: uma busca só, guardada num nome que diz o que o elemento é
const title = document.querySelector(".card__title");

title.classList.add("active");
title.textContent = "Updated";

Escute o clique no pai, e descubra qual filho foi clicado

Registrar um listener em cada card funciona enquanto os cards já estão na tela. No momento em que a lista carrega mais itens por scroll infinito ou por filtro, os cards novos chegam sem listener nenhum, e o clique neles não faz nada.

Um listener só, no container que nunca é recriado, resolve os dois casos. O evento sobe do card clicado até o container, e o closest diz de qual card ele veio. Cards que ainda nem existem já estão cobertos.

❌ Ruim: um listener por card, e os cards carregados depois ficam sem nenhum
document.querySelectorAll(".product-card").forEach((card) => {
  card.addEventListener("click", handleSelect);
});
✅ Bom: um listener no container, e o closest identifica o card de origem
document.getElementById("product-list").addEventListener("click", (event) => {
  if (!event.target.closest(".product-card")) return;

  event.target.closest(".product-card").classList.toggle("selected");
});

fetch fala com a API, e o status precisa ser conferido

O fetch é o $.ajax nativo. Ele devolve uma Promise, e async com await deixa o fluxo legível de cima para baixo.

Uma armadilha aparece aqui: o fetch só rejeita a Promise quando a rede falha. Um 400 ou um 500 do servidor chegam como resposta bem-sucedida, e o .then roda normalmente. É por isso que o orderResponse.ok precisa ser conferido à mão. Sem essa conferência, um pedido recusado pela API cai no caminho de sucesso, e o usuário vê a tela de confirmação de um pedido que nunca existiu.

❌ Ruim: o erro da API entra no caminho de sucesso, e o usuário vê a confirmação
fetch("/api/orders", { method: "POST", body: JSON.stringify(orderData) })
  .then((res) => res.json())
  .then((data) => showSuccess(data));
✅ Bom: o status é conferido, e a resposta de erro vira exceção
async function createOrder(orderData) {
  const orderResponse = await fetch("/api/orders", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(orderData),
  });

  if (!orderResponse.ok) {
    const error = await orderResponse.json();
    throw new Error(error.message ?? "Request failed");
  }

  const createdOrder = await orderResponse.json();
  return createdOrder;
}

DoDocs v3.7.0 · Desenvolvido por @thiagocajadev · Baseado no trabalho de pmndrs/docs · Poimandres.