af aprenda frontend
módulo 04 comportamento

JavaScript: do zero ao blog funcional.

O script.js final em quatro módulos conectados. Checklist de boas práticas e o que esperar do módulo de TypeScript.

Ao longo deste módulo você foi do console.log("script carregado") a um blog completo com comportamento real: toggle de tema persistido, curtidas salvas no localStorage, barra de progresso de leitura e lista de artigos carregada de um arquivo JSON. O código que fazia tudo isso em um único script.js agora está organizado em quatro módulos com responsabilidades bem definidas. Esta lição revisita o caminho percorrido, mostra como as peças se conectam e estabelece o checklist que vai guiar o próximo módulo.

Como os módulos se conectam

O ponto de entrada do blog é main.js. Ele não faz nada diretamente — delega para os três módulos especializados e apenas registra os listeners de eventos que orquestram tudo:

js
// main.js importa de api.js e ui.js
// ui.js importa de utils.js
// api.js não tem dependências internas
// utils.js não tem dependências internas

// fluxo de dados:
// usuario clica → main.js ouve → chama api.js (rede) e ui.js (DOM)
// ui.js usa utils.js (formatação) para montar o HTML
Diagrama de dependências — quem importa de quem.

Essa estrutura tem uma propriedade importante: utils.js não precisa do navegador para funcionar. Você pode importar formatarData ou calcularTempoLeitura e testá-las com qualquer valor, sem abrir um browser. api.js encapsula toda a interação com a rede — se a URL da API mudar, você muda em um único lugar. ui.js sabe onde no DOM as coisas vivem — o main.js não precisa conhecer seletores de CSS.

O main.js completo, com todas as funcionalidades do blog conectadas:

js
import { buscarArtigos, enviarContato } from "./api.js";
import { renderizarCards, exibirNotificacao } from "./ui.js";

// ============================================================
// TOGGLE DE TEMA
// ============================================================
const botaoTema = document.getElementById("botao-tema");
const html = document.documentElement;

const temaSalvo = localStorage.getItem("tema") ?? "claro";
if (temaSalvo === "dark") html.setAttribute("data-theme", "dark");

botaoTema?.addEventListener("click", () => {
  const temaDark = html.getAttribute("data-theme") === "dark";
  const novoTema = temaDark ? "claro" : "dark";

  if (novoTema === "dark") {
    html.setAttribute("data-theme", "dark");
  } else {
    html.removeAttribute("data-theme");
  }

  localStorage.setItem("tema", novoTema);
});

// ============================================================
// BARRA DE PROGRESSO DE LEITURA
// ============================================================
const barraProgresso = document.getElementById("barra-progresso");
const conteudo = document.querySelector(".conteudo-artigo");

window.addEventListener("scroll", () => {
  if (!barraProgresso || !conteudo) return;

  const scrollMaximo = conteudo.offsetHeight - window.innerHeight;
  const progresso = Math.min((window.scrollY / scrollMaximo) * 100, 100);
  barraProgresso.style.width = `${progresso}%`;
});

// ============================================================
// CURTIDAS — delegação de eventos
// ============================================================
const listaArtigos = document.getElementById("lista-artigos");

listaArtigos?.addEventListener("click", (event) => {
  const botao = event.target.closest(".botao-curtir");
  if (!botao) return;

  const card = botao.closest("[data-id]");
  const artigoId = parseInt(card.dataset.id);
  const curtidos = JSON.parse(localStorage.getItem("curtidos")) ?? [];

  if (curtidos.includes(artigoId)) {
    const novos = curtidos.filter(id => id !== artigoId);
    localStorage.setItem("curtidos", JSON.stringify(novos));
    botao.setAttribute("aria-pressed", "false");
    botao.textContent = "🤍 Curtir";
  } else {
    curtidos.push(artigoId);
    localStorage.setItem("curtidos", JSON.stringify(curtidos));
    botao.setAttribute("aria-pressed", "true");
    botao.textContent = "❤️ Curtido";
  }
});

// ============================================================
// CARREGAMENTO DE ARTIGOS
// ============================================================
async function inicializar() {
  if (!listaArtigos) return;

  try {
    const artigos = await buscarArtigos();
    const curtidos = JSON.parse(localStorage.getItem("curtidos")) ?? [];
    listaArtigos.innerHTML = renderizarCards(
      artigos.filter(a => a.publicado),
      curtidos
    );
  } catch {
    listaArtigos.innerHTML = "<p>Erro ao carregar artigos.</p>";
  }
}

inicializar();
main.js — ponto de entrada que orquestra os quatro módulos.

O que cada módulo fez

Cada lição adicionou uma camada ao blog. Revisitar o percurso ajuda a ver o JavaScript como um conjunto coerente, não como uma lista de tópicos isolados.

As lições 1 a 2 estabeleceram o ambiente: o que JavaScript é, onde roda, como o navegador carrega o arquivo com defer. O blog ganhou um script.js vazio que confirmava estar conectado.

As lições 3 a 6 ensinaram a base da linguagem: tipos e variáveis, operadores (incluindo ?? para o fallback do tema no localStorage), controle de fluxo com early return para evitar aninhamento excessivo, funções com parâmetros padrão e closures. Com isso, o toggle de tema ficou funcional — três linhas de JavaScript que persistem e restauram a preferência do usuário.

As lições 7 e 8 cobriram as estruturas de dados: arrays com métodos imutáveis (map, filter, find) e o padrão de atualizar sem mutar ([...curtidos, novoId]), objetos com optional chaining (artigo?.autor?.nome) e spread para atualização imutável. A lista de curtidas no localStorage ficou gerenciada corretamente.

As lições 9 a 11 completaram o vocabulário para trabalhar com texto e com o DOM: template literals para gerar HTML, métodos de string para formatar dados (gerarSlug, truncar descrição), seleção de elementos com querySelector e closest, manipulação com classList, textContent e innerHTML para inserir os cards.

A lição 12 conectou o JavaScript ao usuário via eventos — addEventListener, o objeto Event, e delegação de eventos para gerenciar cliques em qualquer card sem registrar um listener por elemento. A barra de progresso usou o evento scroll no window.

As lições 13 a 15 introduziram o modelo assíncrono: Promises como representação de operações futuras, async/await para consumir Promises com sintaxe linear, e fetch para fazer requisições HTTP reais — com verificação de response.ok e o padrão de dois awaits.

A lição 16 organizou tudo isso em módulos ES com import e export, eliminando o escopo global compartilhado e tornando cada arquivo testável de forma independente.

Checklist JavaScript

Estas práticas aparecem ao longo do módulo por razões específicas. Cada uma protege contra uma classe de erro real:

js
// ✅ const por padrão — reatribuição acidental vira erro de sintaxe
const botaoTema = document.getElementById("botao-tema");

// ✅ === em todas as comparações — sem coerção de tipo surpresa
if (temaSalvo === "dark") { /* ... */ }

// ✅ Optional chaining — sem TypeError quando o elemento não existe na página
botaoTema?.addEventListener("click", handler);

// ✅ Nullish coalescing — fallback só para null/undefined, não para 0 ou ""
const curtidos = JSON.parse(localStorage.getItem("curtidos")) ?? [];

// ✅ textContent para dados — innerHTML interpreta HTML (risco de XSS)
botao.textContent = "❤️ Curtido";

// ✅ Event delegation — um listener para lista inteira, captura elementos dinâmicos
listaArtigos?.addEventListener("click", (event) => {
  const botao = event.target.closest(".botao-curtir");
  if (!botao) return; // early return — sem aninhamento
  // ...
});

// ✅ async/await com try/catch — erros de rede não ficam silenciosos
async function inicializar() {
  try {
    const artigos = await buscarArtigos();
    // ...
  } catch {
    listaArtigos.innerHTML = "<p>Erro ao carregar artigos.</p>";
  }
}

// ✅ Imutabilidade em arrays — o original nunca é afetado
const novos = curtidos.filter(id => id !== artigoId);
localStorage.setItem("curtidos", JSON.stringify(novos));
main.js — as boas práticas embutidas no código do blog.

O que vem a seguir

O blog funciona — mas o JavaScript não tem memória de tipos. Nada impede passar uma string onde um número é esperado, ou chamar .map() em algo que não é um array. Esses erros só aparecem quando o código roda, e frequentemente em produção.

TypeScript resolve isso adicionando anotações de tipo ao JavaScript. O script.js do blog vai ganhar tipos em cada função, em cada objeto, em cada retorno de fetch. O navegador continua recebendo JavaScript puro — TypeScript é uma etapa de compilação que verifica o código antes de rodar.

Os conceitos do módulo não mudam: const, async/await, fetch, módulos. O que muda é que o editor e o compilador passam a detectar erros antes de você abrir o browser.

Resumo

  • O blog final tem quatro arquivos com responsabilidades separadas: utils.js (funções puras), api.js (rede), ui.js (DOM), main.js (orquestração e listeners).
  • const por padrão, let somente quando reatribuição é necessária.
  • === em todas as comparações — nunca ==.
  • Dados do usuário nunca em innerHTML — usar textContent.
  • Event delegation: um listener no pai com event.target.closest() para capturar filhos presentes e futuros.
  • Toda operação assíncrona com try/catch — Promise rejeitada sem tratamento é um bug silencioso.
  • Arrays são atualizados de forma imutável: filter para remover, [...array, item] para adicionar.
  • TypeScript é o próximo passo — mesma linguagem, com verificação de tipos em tempo de compilação.
/ checkpoint verifique seu entendimento
questão 1 de 4

Por que usar const por padrão em vez de let?