Programação Web 2

SPA e Client-Side Frameworks

Prof. Me. Danilo Bueno

Objetivos

  • Entender o conceito de SPA e como surgiu
  • Compreender quais problemas SPAs em vanilla JS criam
  • Entender como os frameworks surgiram como resposta
  • Conhecer o conceito de componentização
  • Saber quais alternativas existem e quando usá-las

Uma breve história

  • 1996 — JavaScript em 10 dias: a web passa a fazer coisas, não só exibir texto
  • 2004 — AJAX + XMLHttpRequest: páginas atualizam sem recarregar → Gmail, Google Maps
  • 2004–2010 — SPAs ganham popularidade; gerenciá-las em vanilla JS se torna doloroso
  • 2010+ — surgem frameworks (Backbone, Ember, Angular, React, Vue) para organizar essa complexidade

Os frameworks não inventaram as SPAs — eles nasceram por causa delas.

O que é uma SPA?

Single Page Application

MPA vs SPA

MPA — Multi Page Application
Usuário clica em "Catálogo"
  → servidor processa a rota
  → retorna HTML completo
  → navegador re-renderiza tudo
SPA — Single Page Application
Usuário clica em "Catálogo"
  → JS intercepta a navegação
  → fetch busca só os dados (JSON)
  → DOM é atualizado pontualmente
O navegador carrega **uma vez** — o resto é atualização de DOM via JavaScript.

O que uma SPA entrega

Característica Benefício
Navegação sem reload Transições instantâneas — sem piscar
Estado persistente Variáveis JS sobrevivem entre "telas"
API-first Back-end retorna JSON — desacoplamento
Assets em cache JS/CSS baixados uma vez, reutilizados
Experiência de app Comportamento próximo de um app nativo

Gmail (2004) e Google Maps (2005) popularizaram o padrão — antes de qualquer framework moderno existir.

SPAs à escala — o problema emerge

Uma SPA pequena em vanilla JS ainda é gerenciável. Conforme cresce:

  • Múltiplos elementos precisam refletir o mesmo dado
  • Estado compartilhado entre partes distantes da tela
  • DOM atualizado manualmente em vários pontos do código
function adicionarAoCarrinho(produto) {
  carrinho.push(produto);
  atualizarHeader(); // ← lembrou?
  atualizarBadge(); // ← lembrou?
  atualizarListaProdutos(); // ← lembrou?
  atualizarResumoLateral(); // ← lembrou?
}

Manter a UI em sincronia com o estado se torna o maior desafio.

O problema central

function adicionarAlbum(album) {
  albuns.push(album);
  renderizarLista(); // atualizar UI
}

function removerAlbum(id) {
  albuns = albuns.filter((a) => a.id !== id);
  renderizarLista(); // atualizar UI
}

function editarAlbum(id, novoTitulo) {
  albuns.find((a) => a.id === id).titulo = novoTitulo;
  renderizarLista(); // atualizar UI
}

A verbosidade do DOM manual

function buildAlbumEl(album) {
  const item = document.createElement("li");
  const span = document.createElement("span");
  span.textContent = album.titulo;
  item.id = "album-" + album.id;
  item.appendChild(span);
  item.appendChild(buildDeleteButtonEl(album.id));
  return item;
}

function renderizarLista() {
  const frag = document.createDocumentFragment();
  albuns.forEach((a) => frag.appendChild(buildAlbumEl(a)));
  while (listaEl.lastChild) listaEl.removeChild(listaEl.lastChild);
  listaEl.appendChild(frag);
}

Frameworks como resposta

As SPAs criaram o contexto — mas o problema que os frameworks vieram resolver é:

Dado um estado, a UI deve refletir esse estado automaticamente — sempre.

Quais frameworks existem?

React, Vue, Angular, Svelte, Solid, Qwik...

Framework Criado por Desde
React Meta (Facebook) 2013
Vue Evan You 2014
Angular Google 2016
Svelte Rich Harris 2016

Todos resolvem o mesmo problema central — de formas diferentes.

Fonte: State of JS 2025

A mesma lista em Vue

<ul>
  <li v-for="album in albuns" :key="album.id">
    <span>{{ album.titulo }}</span>
    <button @click="removerAlbum(album.id)">Excluir</button>
  </li>
</ul>
  • Você descreve o que quer ver, não como fazer.
  • Uma maneira de escrever interfaces de forma mais declarativa.
  • Prover uma melhor experiência de desenvolvimento.

O que os frameworks entregam?

Principais características

  1. DSLs — linguagens de template (JSX, Vue templates, TypeScript)
  2. Componentes — props, state, events
  3. Estilização — CSS scoped, CSS-in-JS
  4. Gerenciamento de dependências — composição de componentes
  5. Renderização — Virtual DOM / Incremental DOM
  6. Roteamento — client-side routing
  7. Ferramental — Vite, hot reload, bundling, testes

1. DSLs — linguagens de template

Framework DSL Parece com
React JSX JavaScript com HTML dentro
Vue Templates HTML HTML com atributos especiais
Angular TypeScript + templates HTML + TypeScript
// React — HTML dentro de JavaScript
function Card({ titulo }) {
  return (
    <div className="card">
      <h2>{titulo}</h2>
    </div>
  );
}
<!-- Vue — JavaScript dentro de HTML -->
<div class="card"><h2>{{ titulo }}</h2></div>

Componentização

A peça fundamental dos frameworks

O problema: HTML que se repete

<!-- Você repetiu este bloco 20 vezes -->
<div class="card">
  <img src="poster1.jpg" alt="Interestelar" />
  <h2>Interestelar</h2>
  <p>Ficção Científica · 2014</p>
  <button>+ Favoritar</button>
</div>

<div class="card">
  <img src="poster2.jpg" alt="Parasita" />
  <h2>Parasita</h2>
  <!-- ... -->
</div>

Precisou mudar o botão para ♥ Favoritar? Atualiza 20 vezes.
Precisou adicionar a nota de crítica? Atualiza 20 vezes.

Funções -> repetição de lógica

// Sem função — lógica repetida
const area1 = 5 * 3;
const area2 = 8 * 4;
const area3 = 2 * 9;

// Com função — lógica encapsulada, reutilizável
function area(largura, altura) {
  return largura * altura;
}
const area1 = area(5, 3);
const area2 = area(8, 4);

Funções encapsulam lógica reutilizável.

Componentes -> repetição de UI

<!-- Em vez de copiar e colar HTML -->
<FilmeCard titulo="Interestelar" nota="8.7" poster="poster1.jpg" />
<FilmeCard titulo="Parasita" nota="8.6" poster="poster2.jpg" />
<FilmeCard titulo="Whiplash" nota="8.5" poster="poster3.jpg" />
  • Um componente é uma tag HTML personalizada com HTML + CSS + JS próprios
  • Mudou alguma coisa? Atualiza um único lugar
  • Passou dados diferentes? Cada instância renderiza o seu próprio conteúdo

A árvore de componentes

Toda aplicação baseada em componentes é uma árvore:

App
├── Header
│   └── NavLink (×3)
├── ListaFilmes
│   └── FilmeCard (×6)
│       ├── PosterImagem
│       └── BotaoFavoritar
└── Footer

Cada nó é um componente. O componente pai passa dados para o filho.

O componente raiz (App) é o ponto de entrada da aplicação.

Anatomia de um componente (SFC)

Em Vue, cada componente é um arquivo .vue

<template>
  <!-- HTML do componente -->
  <div class="card">
    <h2>Interestelar</h2>
  </div>
</template>

<script setup>
  // Lógica do componente (JavaScript)
</script>

<style scoped>
  /* CSS que afeta APENAS este componente */
  .card {
    border-radius: 8px;
  }
</style>

scoped garante que o CSS não "vaza" para outros componentes.

Props — como os dados chegam

O pai passa dados via atributos — esses atributos são chamados props:

<!-- App.vue (pai) envia dados -->
<FilmeCard titulo="Interestelar" :nota="8.7" />
<!-- FilmeCard.vue (filho) recebe -->
<template>
  <div class="card">
    <h2>{{ titulo }}</h2>
    <span>⭐ {{ nota }}</span>
  </div>
</template>

<script setup>
defineProps(["titulo", "nota"]);
</script>

Dados fluem de cima para baixo — pai → filho.

Eventos — como o filho fala com o pai

O filho não modifica props — ele emite eventos para o pai reagir:

<!-- FilmeCard.vue — filho emite -->
<button @click="$emit('favoritar', titulo)">♥ Favoritar</button>
<!-- App.vue — pai escuta e reage -->
<FilmeCard titulo="Interestelar" @favoritar="adicionarAosFavoritos" />

Props descem (pai → filho) · Eventos sobem (filho → pai).

Esse fluxo unidirecional torna a aplicação previsível.

E antes dos frameworks?

A web tentou resolver isso nativamente com Web Components:

class FilmeCard extends HTMLElement {
  connectedCallback() {
    const titulo = this.getAttribute("titulo");
    this.innerHTML = `
      <div class="card"><h2>${titulo}</h2></div>
    `;
  }
}
customElements.define("filme-card", FilmeCard);
<filme-card titulo="Interestelar"></filme-card>

Funciona — mas é verboso, requer muito boilerplate e tem limitações.

Quando usar — e quando não usar

Quando NÃO usar um framework

Overengineering: usar uma ferramenta complexa para um problema simples.

Projeto Framework necessário?
Portfolio pessoal (3 páginas estáticas) ❌ HTML + CSS
Blog com conteúdo fixo ❌ Astro, Jekyll, 11ty
Landing page com formulário simples ❌ HTML + JS vanilla
Dashboard com dados em tempo real ✅ React / Vue
App com múltiplas "telas" e estado compartilhado ✅ React / Vue

Alternativas

Alternativa Quando usar
Site estático (Astro, 11ty) Conteúdo que não muda frequentemente
SSR (Next.js, Nuxt) SEO importante + dados dinâmicos
CMS (WordPress, Strapi) Clientes não-técnicos editam conteúdo
Vanilla JS Interatividade simples e pontual

Este site (danilocbueno.github.io) é construído com Astro — um gerador estático que usa componentes, mas envia HTML puro ao cliente por padrão.

Como escolher um framework?

Perguntas úteis:

  1. Minha equipe já conhece algum?
  2. O projeto precisa de SSR (SEO)?
  3. Qual o tamanho da comunidade e documentação?
  4. Qual o prazo para aprender?

Para aprender — qualquer um dos grandes serve. Os conceitos são transferíveis.

Referências

Obrigado!

[email protected] · website/docs · github