Usar JavaScript vanilla no obliga a escriure codi desordenat. El problema apareix quan es confon “sense framework” amb “sense arquitectura”: selectors repartits per tota la pàgina, esdeveniments duplicats, estat amagat al DOM i estils que només funcionen per accident.

Una arquitectura basada en components pot existir perfectament sense React, Vue o Svelte. La diferència és que has de ser més explícit amb els contractes: què rep cada peça, quin esdeveniment emet, quin estat controla i quina part del DOM li pertany.

Punts clau

  • Vanilla JS funciona bé quan l’estat és local, les interaccions són acotades i el cost de framework no compensa.
  • Un component no és només un bloc HTML: necessita contracte, inicialització, esdeveniments, accessibilitat i neteja.
  • L’arquitectura ha de permetre migrar després sense reescriure-ho tot des de zero.
  • El rendiment millora quan cada component carrega només el que necessita.
  • Si apareix molt estat compartit o rutes complexes, convé avaluar un framework.

Què entenc per component en aquest context

Un component vanilla és una unitat d’interfície amb responsabilitat concreta. Pot ser una targeta interactiva, un cercador, un acordió, un modal, un filtre, un menú o una taula editable. L’important no és la mida, sinó el límit.

Un bon component respon a aquestes preguntes:

PreguntaExemple
Quin DOM controla?Només el node arrel rebut a init()
Quines dades necessita?Props des de data-*, JSON incrustat o API
Quins esdeveniments emet?filter:change, modal:open, form:valid
Quin estat manté?Obert/tancat, valor seleccionat, pàgina actual
Com es destrueix?Neteja de listeners, timers i observers
Sistema de components vanilla amb mòduls independents i límits clars
Una arquitectura de components també pot existir sense JSX ni framework pesat.

Estructura de carpetes que escala sense framework

Una estructura simple pot ser suficient:

src/
  components/
    accordion/
      accordion.js
      accordion.css
      README.md
    lead-filter/
      lead-filter.js
      lead-filter.css
      contract.md
  lib/
    dom.js
    events.js
    fetch-json.js
  pages/
    blog.js
    landing.js

L’objectiu no és imitar un framework, sinó evitar acoblaments invisibles. Cada component hauria d’exposar una funció d’inicialització i, si escau, una funció de destrucció:

export function initAccordion(root) {
  const buttons = [...root.querySelectorAll("[data-accordion-trigger]")];

  function onClick(event) {
    const panel = event.currentTarget.nextElementSibling;
    const isOpen = event.currentTarget.getAttribute("aria-expanded") === "true";
    event.currentTarget.setAttribute("aria-expanded", String(!isOpen));
    panel.hidden = isOpen;
  }

  buttons.forEach((button) => button.addEventListener("click", onClick));

  return () => {
    buttons.forEach((button) => button.removeEventListener("click", onClick));
  };
}

Aquest patró obliga a pensar en ownership. El component no busca per tota la pàgina, no muta nodes aliens i no deixa listeners penjats.

Contractes d'UI entre components amb esdeveniments i límits ben definits
Els contractes clars entre components redueixen acoblament i faciliten l'evolució tècnica.

Contractes abans que convencions fràgils

Quan un component falla en producció, gairebé sempre el problema és en un contracte implícit. Un dissenyador canvia una classe, un editor mou un bloc, un endpoint retorna un camp diferent i el component deixa de funcionar.

Per evitar-ho, cada component hauria de documentar:

  • selector arrel;
  • atributs data-* que consumeix;
  • esdeveniments que escolta i emet;
  • requisits d’accessibilitat;
  • dependències de CSS o tokens;
  • errors esperats;
  • exemple mínim d’HTML.

Exemple de contracte:

<section data-component="lead-filter">
  <button data-filter="enterprise">Empreses</button>
  <button data-filter="agency">Agències</button>
  <div data-results></div>
</section>
import { initLeadFilter } from "./components/lead-filter/lead-filter.js";

document
  .querySelectorAll('[data-component="lead-filter"]')
  .forEach((root) => initLeadFilter(root));

Estat: local per defecte, compartit només quan aporta

Vanilla es complica quan l’estat compartit s’improvisa. Abans de crear un store global, convé preguntar si l’estat realment el necessita.

EstatOn viuriaMotiu
Modal obert/tancatComponentNo afecta la resta
Filtre d’una llistaComponent o URLPot necessitar deep-linking
Usuari autenticatCapa d’aplicacióL’usen diverses peces
Carret o checkoutStore clar o frameworkEstat crític i compartit

Si un projecte comença a necessitar sincronització constant entre components, rutes dinàmiques i actualitzacions reactives, potser la pregunta ja no és “com fer-ho en vanilla”, sinó “quin framework redueix risc”.

Rendiment i càrrega progressiva

Un avantatge real de vanilla és poder carregar poc JavaScript. Però aquest avantatge es perd si cada pàgina importa un bundle comú amb interaccions que no usa. A Astro, per exemple, la filosofia d’illes ajuda precisament a enviar menys JavaScript al client quan la pàgina pot ser estàtica.

Un criteri pràctic:

  1. HTML funcional primer.
  2. CSS sense dependència de JavaScript sempre que sigui possible.
  3. JavaScript només per millorar interacció.
  4. Importació per pàgina o per component.
  5. Mesura de pes, INP i errors de consola.

Quan migrar a un framework

Migrar té sentit quan el cost de mantenir l’arquitectura manual supera el cost del framework. Senyals típiques:

  • moltes peces depenen del mateix estat;
  • hi ha rutes, permisos i vistes dinàmiques complexes;
  • l’equip necessita un patró comú de testing;
  • hi ha duplicació de lògica entre components;
  • les interaccions depenen de dades en temps real;
  • els canvis de producte trenquen peces no relacionades.

La migració no ha de ser total. Es pot aïllar primer la zona amb més complexitat i mantenir vanilla en pàgines estàtiques o components simples.

Lectures relacionades

Conclusió

Vanilla JS no és una excusa per renunciar a l’arquitectura. Al contrari: obliga a ser més conscient de contractes, límits i cost de manteniment. Si el projecte és simple, aquesta claredat pot ser un avantatge enorme. Si deixa de ser-ho, la mateixa arquitectura et donarà una ruta de migració menys traumàtica.

Preguntes freqüents

Té sentit usar components sense React o Vue?
Sí, si el producte té interaccions acotades, poca necessitat d'estat global i prioritat en rendiment. La clau és definir contractes, esdeveniments i límits de responsabilitat.
Quan convé migrar des de vanilla JS a un framework?
Convé migrar quan apareixen estat compartit complex, rutes riques, equips grans, reutilització intensa o proves que es tornen difícils amb mòduls solts.
Què ha de tenir un component vanilla mantenible?
Ha de tenir una API clara, estat local controlat, esdeveniments documentats, accessibilitat, proves mínimes i estils que no depenguin de selectors fràgils.

Tornar a l’arxiu