L'última lliçó del mòdul anterior acabava amb una llista incòmoda: perquè Nómada Tasques funcioni tal com funciona has hagut de construir a mà un render declaratiu, una reconciliació per clau estable, un estat centralitzat amb invalidació, una neteja sistemàtica, una llista virtualitzada i una divisió de codi. Aquesta llista és, gairebé punt per punt, el que un framework modern et lliura resolt el primer dia. Però «t'ho donen resolt» no és una explicació: és un eslògan. Aquesta lliçó és el diagnòstic abans del remei. Veuràs quins problemes concrets apareixen quan es construeix una interfície a mà —els vuit que tu ja has patit i resolt, amb nom i cognoms—, què significa de debò el salt d'imperatiu a declaratiu i l'equació UI = f(estat), com funcionen per dins les tres famílies de reactivitat que es reparteixen el panorama, per què el component és una unitat de reutilització millor que els teus mòduls de vista/, quins tipus d'estat existeixen i per què l'estat del servidor és un problema d'una altra naturalesa, per a què serveix el cicle de vida, què separa una llibreria d'un framework, i —el que gairebé cap tutorial explica— quant costa adoptar-ne un i quan no ho hauries de fer. En acabar tindràs criteri, que és exactament el que cal per llegir les quatre lliçons següents sense deixar-te enlluernar per cap.

Contingut

  1. El diagnòstic abans del remei
  2. Els vuit problemes de construir una interfície a mà
  3. La taula del diagnòstic: problema → la teva solució → com ho resol un framework
  4. Imperatiu davant de declaratiu
  5. UI = f(estat): l'equació i les seves conseqüències
  6. El mateix tros de tauler, escrit de les dues maneres
  7. Què significa «reactivitat» exactament
  8. Família 1: DOM virtual i reconciliació
  9. Família 2: reactivitat de gra fi amb senyals
  10. Família 3: compilació en temps de construcció
  11. Les tres famílies en una taula
  12. El component: plantilla, estat, comportament i estils
  13. Per què el component és millor unitat que els teus mòduls de vista/
  14. Estat: local, elevat, compartit i de servidor
  15. Per què l'estat del servidor és un problema diferent
  16. El cicle de vida i per què existeix
  17. Llibreria davant de framework: què significa «amb opinions»
  18. El cost real d'adoptar un framework
  19. Quan NO fer servir un framework
  20. El panorama actual per situar-se
  21. Què fa aquest mòdul i què no fa
  22. Errors Habituals i Consells
  23. Exercicis
  24. Conclusió

  1. El diagnòstic abans del remei

Hi ha dues maneres d'aprendre un framework. La primera és l'habitual: obrir la documentació, copiar el «hola món», aprendre la sintaxi i descobrir sis mesos després —de vegades mai— quin problema resolia cada peça. La segona és la que et pots permetre tu, i només tu, perquè has fet el camí llarg: mirar primer el problema, comprovar que l'has patit i només llavors mirar la solució que proposa cada eina.

La diferència no és d'estil. Qui aprèn per la primera via acaba fent servir useMemo arreu «perquè optimitza», ficant tot l'estat a Redux «perquè és el que fan els professionals» i triant framework pel que ha vist en una xerrada. Qui aprèn per la segona es pregunta una altra cosa: quin problema concret tinc jo, quant em costa ara i quant em costaria amb això?

Nómada Tasques és avui una aplicació real: sis tasques al tauler, 48 hores totals, 45 obertes, 600 tasques a la prova de càrrega, render() en 31 ms, 1.194 nodes, 58,3 kB en tres peticions i un LCP d'1,9 s. Cap d'aquests números va sortir de franc. Cadascun va costar una lliçó, i uns quants en van costar dues. Aquest cost és la dada que necessites per jutjar qualsevol framework: no es compara contra un ideal, es compara contra el que ja tens.

Un advertiment de mètode abans de començar. Aquest mòdul no et farà expert en React, Vue o Angular. Quatre lliçons no donen per a això, i qui et digui el contrari t'està venent alguna cosa. El que sí que et donarà és el model mental de cadascun: quin problema resol, amb quin mecanisme, a canvi de què. Amb aquest model, aprendre'n qualsevol de debò passa de ser un mes de desconcert a ser una setmana de llegir documentació entenent el que llegeixes.

  1. Els vuit problemes de construir una interfície a mà

Els anomenarem. No són «problemes de JavaScript»: són problemes de qualsevol interfície que mostri dades que canvien. Apareixen a Java Swing, a Android natiu, a iOS i al web. Els frameworks web moderns no els van inventar; van heretar quaranta anys d'intents.

Problema 1 · La sincronització entre estat i pantalla. La dada viu en una variable; la pantalla la mostra en un <span>. Quan la dada canvia, algú s'ha de recordar d'actualitzar el <span>. Si hi ha tres llocs que mostren les hores obertes —el resum, la capçalera i el títol de la pestanya—, hi ha tres llocs per actualitzar, i la fallada consisteix a oblidar-ne un. És l'error més habitual de la programació d'interfícies i el més difícil de detectar amb proves, perquè la pantalla no «falla»: menteix.

Problema 2 · La identitat dels nodes en redibuixar. La manera mandrosa de resoldre el problema 1 és redibuixar-ho tot. Funciona, però destrueix els nodes i, amb ells, el focus, la selecció de text, la posició de desplaçament, les transicions CSS en curs i l'estat intern del navegador (un <details> obert, un vídeo reproduint-se). Ho vas veure a 06-06 amb replaceChildren.

Problema 3 · El rendiment del redibuixat complet. Amb sis tasques ningú no ho nota. Amb 600 i un input que es dispara a cada tecla, redibuixar-ho tot són 310 ms de bloqueig per pulsació. Ho vas mesurar a 09-01 i ho vas deixar en 31 ms a 09-04.

Problema 4 · La neteja. Cada addEventListener, cada setInterval, cada IntersectionObserver, cada subscripció a un WebSocket és una cosa que cal apagar quan el seu tros de pantalla desapareix. Si no s'apaga, tens una fuita de memòria i, pitjor encara, un gestor que continua reaccionant a esdeveniments d'alguna cosa que ja no existeix. Va ser tot el 09-03, amb destruir() i AbortController.

Problema 5 · La composició. Una targeta de tasca apareix al tauler, al plafó de detalls i a l'informe. Si és una funció solta que rep un <li> i el modifica, reutilitzar-la en un altre context exigeix que aquest context sàpiga massa coses d'ella. La reutilització de trossos d'interfície amb el seu estat i el seu comportament a dins no és un problema que resolguin les funcions soltes.

Problema 6 · La comunicació entre parts llunyanes. El filtre per responsable el canvia un <select> de la capçalera, i afecta la llista, el resum, el comptador de la pestanya i l'URL. Tu ho vas resoldre amb CustomEvent a 06-04, que funciona però converteix el flux de dades en una cosa que només es pot seguir cercant cadenes de text per tot el projecte.

Problema 7 · L'acoblament entre estructura, estil i comportament. L'HTML de la targeta és en un <template> d'index.html, els seus estils a css/estils.css i el seu comportament repartit entre targeta.js i controlador.js. Són quatre fitxers per a una sola cosa. Canviar el nom d'una classe CSS exigeix tocar-ne dos i confiar en la memòria.

Problema 8 · Les convencions d'equip. Tu vas decidir que les vistes exposen render, actualitzar i destruir; que els esdeveniments personalitzats viuen a ESDEVENIMENTS; que les accions es declaren amb data-accio. Són bones decisions, però són teves. Una altra persona que entri al projecte les ha d'aprendre llegint el codi, perquè no estan escrites enlloc tret del costum.

  1. La taula del diagnòstic: problema → la teva solució → com ho resol un framework

Aquesta és la taula central de la lliçó. Llegeix-la a poc a poc: la columna del mig és teva, i per això s'entén la de la dreta.

# Problema La teva solució a Nómada Tasques Com ho resol un framework
1 Sincronitzar estat i pantalla El cicle estat → render() → esdeveniment → nou estat → render() de 06-06, amb una única funció que descriu la pantalla sencera De fàbrica: descrius la pantalla en funció de l'estat i el framework s'encarrega de tornar-la a executar quan l'estat canvia
2 Identitat dels nodes reconciliar(contenidor, dades, clau, pintar) amb data-id (06-06) La key de React, el :key de Vue i el track d'Angular. És literalment el teu data-id, elevat a requisit de l'API
3 Rendiment del redibuixat Índex Map, memòria cau per versió (09-02), lots d'escriptura i requestAnimationFrame (09-04), virtualització (09-04) Reconciliació incremental o actualització de gra fi; la virtualització continua sent teva (amb llibreries)
4 Neteja destruir() a cada vista + AbortController compartit (09-03) El cicle de vida: la funció de neteja d'useEffect, onUnmounted, ngOnDestroy. Es crida sola
5 Composició Mòduls vista/*.js que exporten funcions i classes Components: plantilla, estat, comportament i estils en una sola unitat instanciable
6 Comunicació entre parts llunyanes CustomEvent + ESDEVENIMENTS (06-04) props cap avall, esdeveniments cap amunt, i un magatzem compartit quan això no n'hi ha prou (ho veuràs a 10-03)
7 Acoblament estructura/estil/comportament Quatre fitxers per targeta: <template>, CSS, targeta.js, controlador.js Un fitxer per component, amb estils d'àmbit propi
8 Convencions d'equip Les teves, sense escriure Les del framework, documentades, amb eines i amb gent que ja les coneix

Dues lectures d'aquesta taula, totes dues importants.

La primera: cap fila no diu «impossible». Tot el que fa un framework es pot fer a mà, i tu ho has fet. La diferència no és de capacitat, és de cost marginal: la fila 2 et va costar vint línies i mitja lliçó; a React és una propietat key que escrius sense pensar.

La segona, menys evident: les files 7 i 8 no són tècniques. Són d'organització. I en un equip de cinc persones acostumen a pesar més que les sis primeres juntes, perquè el cost real d'un projecte no és escriure el codi sinó que cinc persones l'entenguin alhora durant tres anys.

  1. Imperatiu davant de declaratiu

Aquí hi ha el salt conceptual del mòdul, i convé definir-lo bé perquè les dues paraules es fan servir malament contínuament.

Programació imperativa: descrius els passos per arribar al resultat. «Agafa el node amb id 3, treu-li la classe tasca--pendent, posa-li tasca--feta, canvia el text del botó a "Reobrir", inhabilita l'altre botó i actualitza el comptador de la columna restant-li una unitat.»

Programació declarativa: descrius el resultat i deixes que un altre calculi els passos. «Una tasca en estat feta es veu així.» Si l'estat és feta, la pantalla mostra això; com s'hi arriba des del que hi havia abans no és cosa teva.

Ja coneixes la distinció sense haver-la anomenada. A 04-04 vas comparar això:

// Imperatiu: els passos
const obertes = [];
for (let i = 0; i < backlog.length; i++) {
  if (backlog[i].estat !== 'feta') {
    obertes.push(backlog[i]);
  }
}

amb això:

// Declaratiu: el resultat
const obertes = backlog.filter((t) => t.estat !== 'feta');

El segon no és «més curt»: és d'una altra naturalesa. No esmenta l'índex i, ni l'array de destinació, ni l'ordre del recorregut. Descriu què és obertes (les tasques l'estat de les quals no és 'feta') i deixa els passos al motor. Si demà JavaScript decidís paral·lelitzar filter, el teu codi continuaria sent correcte; el bucle amb índexs, no necessàriament.

Aplicar aquesta mateixa distinció a la pantalla és el que fan els frameworks. I hi ha una asimetria brutal en el nombre de casos que cal considerar.

  • En mode imperatiu, per passar d'un estat a un altre has d'escriure la transició. Amb n estats possibles, hi ha n×(n−1) transicions. Amb cinc estats d'una targeta (pendent, en curs, feta, vençuda, sense assignar) són 20 transicions. Ningú no les escriu totes; s'escriuen les que es recorden, i les fallades viuen en les que no.
  • En mode declaratiu, escrius n descripcions: com es veu cada estat. Les transicions les calcula el framework. Cinc descripcions en lloc de vint transicions.

Aquest és l'argument sencer. No és elegància: és que el nombre de coses que has d'escriure a mà passa de créixer al quadrat a créixer de forma lineal.

graph LR
  subgraph Imperatiu
    A1[Estat A] -->|transició 1| B1[Estat B]
    B1 -->|transició 2| C1[Estat C]
    A1 -->|transició 3| C1
    C1 -->|transició 4| A1
    B1 -->|transició 5| A1
    C1 -->|transició 6| B1
  end
  subgraph Declaratiu
    A2[Estat A] --> V[la vista descriu l'estat]
    B2[Estat B] --> V
    C2[Estat C] --> V
  end

  1. UI = f(estat): l'equació i les seves conseqüències

La forma condensada de tot això anterior és una equació que veuràs a la documentació de gairebé tots els frameworks moderns:

UI = f(estat)

La interfície és el resultat d'aplicar una funció pura a l'estat. Amb el mateix estat, la mateixa pantalla, sempre. Sense estat amagat al DOM, sense «depèn del que hi hagués abans».

Les conseqüències són més profundes del que sembla:

  1. La pantalla es torna raonable. Per saber per què es veu una cosa, mires l'estat. No has de reconstruir mentalment la seqüència de clics que hi va portar. Això és exactament el que feia tan difícil depurar interfícies imperatives: l'error no era a l'estat actual sinó en una transició que va passar fa trenta segons.
  2. La pantalla es torna comprovable. Si f és una funció, es prova com una funció: li dones un estat, comproves la sortida. És el que ja vas fer a 08-05 amb Testing Library, renderitzant la vista amb un tauler conegut i consultant per rol.
  3. El DOM deixa de ser la font de la veritat. Aquest és el canvi mental més gran. En una interfície imperativa, «quantes tasques hi ha?» de vegades es respon comptant <li>. En una de declarativa, això és un error conceptual: el <li> és una conseqüència, no una dada. La font és l'estat.
  4. Torna el problema del rendiment. Si f produeix la pantalla sencera, executar-la a cada canvi significa recrear-ho tot. Aquí és on entra la reactivitat, que és el mecanisme amb què cada framework evita pagar aquest preu. D'això van els apartats 7 a 11.

Convé ser honest amb una cosa: UI = f(estat) és un model, no una descripció literal. Hi ha estat que el DOM guarda de debò i que la funció no controla: la posició del cursor en un <input>, el desplaçament d'un contenidor, quin element té el focus. Els frameworks s'esforcen molt a preservar aquest estat mentre fan veure que ho redibuixen tot, i aquest esforç és precisament la reconciliació. Quan falla —i falla— apareixen les fallades estranyes: un <input> que perd el focus mentre escrius, una animació que es reinicia. En reconeixeràs la causa immediatament, perquè és el teu problema 2.

  1. El mateix tros de tauler, escrit de les dues maneres

Res d'això no s'entén sense codi. Prenguem l'operació més simple de Nómada Tasques: marcar la tasca 6 com a feta, amb les seves conseqüències visibles (la targeta canvia d'aspecte, el botó canvia de text, el comptador de pendents baixa, el de fetes puja i les hores obertes passen de 45 a 40).

La versió imperativa, escrita a mà

// Imperatiu: modifiquem la pantalla pas a pas
function marcarFetaImperatiu(id) {
  const tasca = tauler.cercarPerId(id);
  tasca.canviarEstat('feta');                         // 1 · el model

  const li = document.querySelector(`[data-id="${id}"]`);
  li.dataset.estat = 'feta';                          // 2 · l'atribut
  li.classList.add('tasca--feta');                    // 3 · la classe
  li.classList.remove('tasca--vencuda');              // 4 · ja no pot estar vençuda (R10)

  const avancar = li.querySelector('[data-accio="avancar"]');
  avancar.disabled = true;                            // 5 · no hi ha estat següent
  avancar.textContent = 'Feta';                       // 6 · el text del botó

  const reobrir = li.querySelector('[data-accio="reobrir"]');
  reobrir.disabled = true;                            // 7 · des de 'feta' no es reobre (R6)

  const columnaFetes = document.querySelector('[data-columna="feta"] .llista-tasques');
  columnaFetes.append(li);                            // 8 · moure de columna

  actualitzarComptador('pendent');                    // 9 · comptadors
  actualitzarComptador('feta');                       // 10
  document.querySelector('#hores-obertes').textContent =
    `${tauler.horesObertes()} h`;                     // 11 · el resum
  document.title = `Nómada Tasques (${tauler.resum().pendents})`;  // 12 · la pestanya
}

Dotze passos. Tots correctes. I ara la pregunta que importa: què passa si demà afegim un distintiu de prioritat a la targeta? Resposta: cal recordar-se d'actualitzar-lo aquí, i també a marcarEnCursImperatiu, i a reobrirImperatiu, i a assignarResponsableImperatiu. Quatre llocs. La fallada consisteix a tocar-ne tres.

La versió declarativa, la que ja vas escriure

// Declaratiu: descrivim la pantalla i la tornem a descriure
function marcarFetaDeclaratiu(id) {
  tauler.canviarEstat(id, 'feta');   // 1 · el model, i només el model
  render();                          // 2 · torna a descriure la pantalla sencera
}

function render() {
  const visibles = tasquesVisibles(estat);

  reconciliar(llista, visibles, (t) => t.id, (t, node) => pintarTargeta(t, node, AVUI));

  $('#hores-obertes').textContent = `${estat.tauler.horesObertes()} h`;
  document.title = `Nómada Tasques (${estat.tauler.resum().pendents})`;
}

Dos passos. I el distintiu de prioritat de l'exemple anterior s'afegeix en un sol lloc: dins de pintarTargeta, que és la descripció de com es veu una tasca. Totes les accions que provoquin un render() el veuran actualitzat, sense que ningú se n'hagi de recordar.

Compara honestament les dues:

Aspecte Imperatiu Declaratiu
Línies per acció ~12, i creixen amb la interfície 2, constants
Llocs a tocar en afegir una dada visible Un per cada acció existent Un, la descripció
Risc de pantalla desincronitzada Alt: depèn de la memòria Nul per construcció
Feina del navegador Mínima: només el que canvia Potencialment molta: cal comparar-ho tot
Facilitat de depuració Baixa: cal reconstruir la seqüència Alta: mira l'estat
Facilitat de prova Baixa: cal simular la seqüència Alta: estat → sortida

Fixa't en l'única fila on guanya l'imperatiu: la feina del navegador. Aquesta fila és la factura del model declaratiu, i és exactament la que vas pagar amb reconciliar, amb la memòria cau per versió i amb la virtualització. Els frameworks paguen aquesta mateixa factura, amb mecanismes diferents. Anem-hi.

graph TD
  E["Estat<br/>tauler + filtres"] --> F["f(estat)<br/>descripció de la pantalla"]
  F --> R["Motor de reconciliació<br/>calcula el canvi mínim"]
  R --> D["DOM real"]
  D -->|esdeveniment de l'usuari| A["Acció"]
  A -->|nou estat| E

  1. Què significa «reactivitat» exactament

«Reactiu» és la paraula més gastada del vocabulari del frontend. El seu significat tècnic, però, és precís:

Un sistema és reactiu quan una dependència declarada entre dos valors es manté automàticament: si canvia l'origen, la destinació s'actualitza sense que ningú ho demani.

L'exemple canònic no és de programació, és d'un full de càlcul. Escrius a C1 la fórmula =A1+B1. Canvies A1 i C1 s'actualitza. No has «cridat» res: has declarat una relació i el sistema la manté. Aquesta és tota la idea.

En una interfície hi ha dues relacions per mantenir:

  1. Estat → estat derivat: si canvien les tasques, canvien les hores obertes, el nombre de pendents i la llista filtrada.
  2. Estat → pantalla: si canvien les hores obertes, canvia el <span> que les mostra.

Tu mantens la primera amb la memòria cau per versió de 09-02 (recalcular quan el comptador de versió no coincideix) i la segona cridant render() a mà. Funciona, però té dos forats que coneixes bé: si oblides #invalidar() en una mutació, la memòria cau menteix; i si oblides cridar render(), la pantalla menteix. Un sistema reactiu elimina els dos oblits possibles. Aquest és tot el seu valor.

La pregunta interessant és com se n'assabenta el sistema, que alguna cosa ha canviat. I aquí és on el món es divideix en tres famílies.

  1. Família 1: DOM virtual i reconciliació

Qui la fa servir: React (i, amb matisos, Preact i Inferno).

El mecanisme. La teva funció f(estat) no toca el DOM. Retorna una descripció de la pantalla: un arbre d'objectes JavaScript lleugers, amb el tipus de cada element, els seus atributs i els seus fills. Aquest arbre és el DOM virtual. És la mateixa idea que el teu pintarTargeta, però en lloc de crear un <li> real crearia una cosa així:

// El que retornaria una descripció virtual d'una targeta
{
  tipus: 'li',
  props: { className: 'tasca tasca--alta', 'data-id': 6 },
  fills: [
    { tipus: 'h3', props: { className: 'tasca__titol' }, fills: ['Pressupost de la fusteria'] },
    { tipus: 'button', props: { 'data-accio': 'avancar' }, fills: ['Començar'] }
  ]
}

Quan l'estat canvia, el framework torna a executar f, obté un arbre nou i compara el nou amb l'anterior (el diffing). D'aquesta comparació surt una llista d'operacions mínimes sobre el DOM real: «canvia aquest text», «treu aquesta classe», «elimina aquest node». Només s'apliquen aquestes operacions.

Per què necessita claus. Comparar dues llistes de fills és un problema difícil en el cas general. L'algorisme òptim de diferència entre arbres és de cost cúbic, inviable. Els frameworks fan servir heurístiques de cost lineal, i la més important és: si dos elements ocupen la mateixa posició i tenen el mateix tipus, són el mateix element. Aquesta heurística falla exactament quan la llista es reordena o s'elimina un element del mig — el problema 2, el que vas resoldre amb data-id. La solució és idèntica a la teva: demanar al programador una clau estable que identifiqui cada element independentment de la seva posició. A React es diu key.

Les contrapartides, amb honestedat:

  • Es paga memòria: hi ha dos arbres virtuals vius a cada actualització.
  • Es paga CPU: cal construir l'arbre nou sencer i recórrer-lo comparant, encara que només hagi canviat una paraula. Amb 600 tasques, això són 600 descripcions creades per descobrir que n'ha canviat una.
  • El cost no depèn de quant ha canviat, sinó de quant n'hi ha. Aquest és el seu taló d'Aquil·les, i la raó per la qual apareixen useMemo, React.memo i companyia: són maneres de dir-li al framework «no baixis per aquesta branca, no ha canviat».
  • A canvi, el model mental és molt simple: és una funció que es torna a executar. No hi ha subscripcions invisibles ni objectes embolcallats en proxies. Quan alguna cosa va malament, el que ha passat és que la funció s'ha executat, o no s'ha executat.

  1. Família 2: reactivitat de gra fi amb senyals

Qui la fa servir: Vue (amb ref/reactive/computed), Angular modern (signal), Solid, Svelte en la seva versió amb runes, Preact Signals i pràcticament tot el que és nou.

El mecanisme. En lloc de comparar arbres, el sistema registra què depèn de què mentre s'executa. Un valor reactiu (un senyal) no és un valor: és un valor amb una llista de subscriptors. Quan es llegeix dins d'un context de seguiment, el senyal apunta aquest context com a dependent seu. Quan s'escriu, notifica tots els seus dependents.

En pseudocodi, i simplificant molt:

// Una implementació mínima de senyal, per entendre el mecanisme
let observadorActual = null;

function senyal(valorInicial) {
  let valor = valorInicial;
  const subscriptors = new Set();

  return {
    get() {
      if (observadorActual) subscriptors.add(observadorActual);  // ← registre automàtic
      return valor;
    },
    set(nou) {
      if (Object.is(valor, nou)) return;      // sense canvi, sense feina
      valor = nou;
      for (const s of [...subscriptors]) s();  // ← notificació
    }
  };
}

function efecte(fn) {
  const executar = () => {
    observadorActual = executar;   // mentre corre fn, les lectures s'apunten
    try { fn(); } finally { observadorActual = null; }
  };
  executar();
}

Amb això ja funciona l'essencial:

const horesObertes = senyal(45);

efecte(() => {
  document.querySelector('#hores-obertes').textContent = `${horesObertes.get()} h`;
});

horesObertes.set(40);   // el <span> s'actualitza sol: ningú no ha cridat render()

Llegeix un altre cop aquest últim bloc, perquè conté la idea sencera de la família. Ningú no ha cridat render(). L'efecte es va apuntar com a dependent en llegir el senyal, i el senyal el va avisar en canviar. No hi ha comparació d'arbres, no hi ha recorregut: hi ha una llista de subscriptors i una crida.

La conseqüència decisiva: el cost d'una actualització és proporcional a quant ha canviat, no a quant hi ha en pantalla. Canviar les hores obertes actualitza un node de text, tant si el tauler té 6 tasques com si en té 600. És, conceptualment, la mateixa diferència que hi va haver a 09-02 entre recórrer un array i consultar un Map.

Les contrapartides, amb la mateixa honestedat:

  • El seguiment té cost de memòria i d'establiment: cada valor reactiu arrossega la seva llista de subscriptors.
  • El model mental és més subtil. Com que el registre passa en llegir, hi ha maneres de «perdre» la reactivitat sense adonar-te'n: desestructurar un objecte reactiu copia el valor i trenca el vincle; llegir dins d'un setTimeout no es registra perquè el context ja s'ha tancat. Són els errors clàssics de Vue, i els veuràs amb nom i exemple a 10-04.
  • Quan alguna cosa no s'actualitza, la causa és invisible: no hi ha una crida que falti, hi ha una dependència que no es va registrar. Depurar això requereix eines específiques.
  • A canvi, el rendiment per defecte és millor i calen moltes menys optimitzacions manuals. A la pràctica, useMemo té menys equivalents necessaris al món dels senyals.

  1. Família 3: compilació en temps de construcció

Qui la fa servir: Svelte va ser el primer a fer-ne bandera; Solid compila les plantilles JSX a operacions directes del DOM; Angular compila les seves plantilles des de sempre; Vue compila les seves i fa servir el que ha après per saltar-se trossos de l'arbre que no poden canviar.

El mecanisme. Les dues famílies anteriors resolen el problema al navegador, en temps d'execució. Aquesta el resol abans: un compilador llegeix el teu component durant la construcció del projecte i genera codi JavaScript que actualitza el DOM directament, sense cap motor genèric que l'interpreti.

Si escrius una plantilla que diu «aquí va el nombre d'hores obertes», el compilador pot veure que aquest és l'únic punt que depèn d'aquesta variable i generar, literalment, node7.textContent = horesObertes. No hi ha comparació ni subscripció genèrica: hi ha una assignació, escrita per una màquina que ja sabia on era el forat.

Les contrapartides:

  • Avantatge gran: el motor gairebé desapareix del paquet. El que es descarrega és el teu codi compilat més un grapat d'utilitats, no un motor de reconciliació complet. És la raó per la qual els frameworks compilats tenen paquets base tan petits.
  • Avantatge segon: la feina d'anàlisi es fa una vegada a la teva màquina, no un milió de vegades als mòbils dels usuaris.
  • Desavantatge: el codi que escrius no és JavaScript. És un llenguatge de plantilles que s'assembla a l'HTML i que només entén aquell compilador. Les eines (editor, ESLint, comprovador de tipus, depurador) necessiten complements específics, i el codi que veus al depurador no és el que vas escriure.
  • Desavantatge segon: la màgia es paga en sorpreses. Quan el compilador no pot saber estàticament de què depèn una cosa, ha de recórrer a mecanismes de temps d'execució, i allà sorgeixen regles estranyes que cal memoritzar.
  • A la pràctica les tres famílies s'estan barrejant: React té un compilador que insereix memoïtzació automàticament; Vue compila i fa servir senyals alhora; Angular compila i ha adoptat senyals. La frontera és cada cop menys nítida.

  1. Les tres famílies en una taula

Criteri DOM virtual Senyals (gra fi) Compilació
Quan es decideix què actualitzar En execució, comparant arbres En execució, seguint dependències En construcció, analitzant la plantilla
Unitat que es torna a executar El component sencer L'expressió concreta que depèn de la dada La instrucció generada
Cost d'una actualització Proporcional a la mida de l'arbre Proporcional al que ha canviat Mínim: assignació directa
Necessita claus a les llistes Sí (key) Sí (:key, track)
Mida del motor descarregat Més gran Mitjana Molt petita
Model mental Simple: és una funció que es reexecuta Subtil: hi ha dependències implícites Opac: el codi real l'escriu el compilador
Optimització manual habitual Freqüent (memoïtzar, evitar renders) Poc freqüent Gairebé mai
Fallades típiques Renders de més, key incorrecta Reactivitat perduda en desestructurar Regles del compilador poc intuïtives
Eines de tercers Màximes Bones Requereixen suport específic
Exemples React Vue, Angular, Solid Svelte, Solid, Angular

Un advertiment que estalvia discussions estèrils: cap de les tres no és «la correcta». Cadascuna optimitza una cosa diferent. El DOM virtual optimitza la simplicitat del model mental i la llibertat (tot és JavaScript normal). Els senyals optimitzen el rendiment per defecte. La compilació optimitza la mida i l'arrencada. A la majoria d'aplicacions reals, amb llistes de desenes d'elements, les tres són prou ràpides i la decisió es pren per altres motius: equip, ecosistema, contractació.

  1. El component: plantilla, estat, comportament i estils

El segon concepte gran del mòdul, i el que més s'assembla a una cosa que ja saps d'altres parts de l'enginyeria: la unitat de reutilització.

Un component és una unitat que agrupa quatre coses que fins ara tenies separades:

Part Què és A Nómada Tasques avui
Plantilla L'estructura HTML que produeix <template id="plantilla-tasca"> a index.html
Estat Les dades que només li importen a ell Repartit: unes a estat, altres en atributs del DOM
Comportament Què fa en rebre esdeveniments controlador.js, per delegació
Estils El seu aspecte Un tros de css/estils.css, amb classes .tasca__*

Un component els posa junts i amb un contracte explícit: rep dades per les seves props, emet esdeveniments cap amunt i ocupa un tros de pantalla del qual és enterament responsable.

Tres propietats fan que aquesta unitat funcioni tan bé:

  1. S'instancia. No és «la targeta»: és una plantilla de la qual es creen sis targetes, cadascuna amb el seu propi estat intern (per exemple, si té el menú d'accions desplegat). Amb el teu pintarTargeta, aquest estat per targeta no té cap lloc natural on viure i acaba en un dataset o en un Map extern.
  2. Es compon. Un component conté altres components, i el resultat continua sent un component. <Tauler> conté tres <Columna>, i cadascuna conté n <TargetaTasca>. És exactament el mateix que fa l'HTML, però amb les teves pròpies etiquetes.
  3. Aïlla. Els estils d'àmbit propi impedeixen que la classe .titol de la targeta afecti la .titol de la capçalera. És l'equivalent visual del que els mòduls ES de 05-04 van fer amb els noms de variables: acabar amb l'espai de noms global.

  1. Per què el component és millor unitat que els teus mòduls de vista/

Comparem-ho amb el que tens. js/vista/targeta.js exporta pintarTargeta(tasca, existent, avui). És una bona funció: pura en la seva intenció, serveix per crear i actualitzar, i no depèn de la resta. Però mira'n les costures:

// El contracte real de la teva targeta, repartit per quatre llocs
pintarTargeta(tasca, existent, avui)          // js/vista/targeta.js
<template id="plantilla-tasca">…</template>   // index.html  ← acoblament per id global
.tasca, .tasca--alta, .tasca__titol …         // css/estils.css ← acoblament per nom de classe
[data-accio="avancar"]                        // js/vista/controlador.js ← acoblament per atribut

Quatre acoblaments, i cap no el comprova ningú. Si canvies el nom de plantilla-tasca, res no falla fins que s'executa. Si canvies .tasca__titol al CSS, la targeta es veu malament però no hi ha cap error. Si escrius data-accio="avansar", el botó simplement no fa res. Són fallades que només detecta una prova d'extrem a extrem, i per això et van costar tres recorreguts de Cypress a 08-06.

Ara els tres problemes que un component resol i la teva funció no:

Estat per instància. Si cada targeta necessita saber si el seu menú està desplegat, on viu aquesta dada? En el teu disseny, o la fiques a l'objecte Tasca (contaminant el model amb assumptes de la vista), o la guardes en un Map de la vista indexat per id (i te'n recordes de netejar-lo quan la tasca desapareix, o tens la fuita de 09-03). En un component, és una variable local del component i desapareix amb ell.

Cicle de vida per instància. Si la targeta d'una tasca vençuda necessita un temporitzador que actualitzi «vençuda fa 15 d» a mitjanit, avui has de crear el temporitzador en algun lloc i recordar-te de cancel·lar-lo quan la targeta s'elimina a reconciliar. El teu reconciliar no avisa ningú que ha eliminat un node. En un component, hi ha un punt de desmuntatge que es crida sol.

Contracte explícit. pintarTargeta(tasca, existent, avui) no diu quins camps de tasca fa servir. Un component declara les seves props, i amb TypeScript (que veuràs treure el cap a 10-05) aquesta declaració es comprova en la compilació.

Dit això, siguem justos: el teu mòdul vista/ és el correcte per a la mida de Nómada Tasques. Sis tasques, una pantalla, un desenvolupador. El component comença a compensar quan hi ha vint tipus d'element reutilitzables, cadascun amb estat propi, i diverses persones tocant-los alhora.

  1. Estat: local, elevat, compartit i de servidor

El tercer concepte gran. Gairebé tot el patiment de les aplicacions grans ve de no distingir quatre coses que es diuen igual.

Tipus Què és Exemple a Nómada Tasques On ha de viure
Local Només li importa a un tros d'interfície Si el menú d'accions d'una targeta està desplegat Dins del component
Elevat El comparteixen dos germans, així que puja al pare comú El filtre de responsable, que afecten el <select> i la llista A l'avantpassat comú més proper
Compartit (global) El necessiten parts molt allunyades de l'arbre L'usuari identificat, el tema clar/fosc, l'idioma En un magatzem compartit
De servidor És una còpia local d'una dada que viu en un altre lloc Les tasques que retorna llistarTasques() de 07-02 En una memòria cau amb regles pròpies

La regla pràctica, en un ordre que estalvia molta feina: comença sempre per local i puja només quan t'hi obliguin. L'error més car i més freqüent del frontend modern és el contrari: ficar-ho tot en un magatzem global des del primer dia «per si de cas». El resultat és un objecte gegant on tot depèn de tot, on no es pot esborrar res per por, i on una prova unitària necessita muntar l'aplicació sencera.

Els dos primers tipus ja els coneixes sense anomenar-los: el teu objecte estat d'app.js, amb { tauler, filtres, ordre }, és exactament estat elevat, pujat fins al capdamunt perquè el comparteixen la llista, el resum i l'encaminador. I funciona. La pregunta de 10-03 serà: a partir de quina mida deixa de funcionar?

  1. Per què l'estat del servidor és un problema diferent

Aquesta distinció és de les més útils de tota la lliçó i tota la indústria la va entendre tard.

L'estat local és teu: tu el crees, tu el canvies, tu ets l'única font de veritat. L'estat del servidor no és teu. Tu en tens una còpia, obtinguda en un moment concret, d'una cosa que viu en una altra màquina i que pot canviar sense avisar-te. Això canvia per complet les preguntes que cal respondre:

Pregunta Estat local Estat del servidor
Qui és la font de la veritat? La teva aplicació El servidor
Pot quedar obsolet? No Sí, en qualsevol moment
Cal tornar-lo a demanar? Mai Sí: en enfocar la finestra, en reconnectar, cada n segons
Pot fallar en llegir-lo? No Sí: xarxa, 500, timeout
Hi ha estats intermedis? No Sí: carregant, error, reintentant, obsolet-però-mostrable
Hi ha concurrència? Poca Sí: dues peticions que tornen desordenades
Es comparteix entre pantalles? De vegades Gairebé sempre, i convé una sola memòria cau

Tu ja has viscut totes aquestes files. A 07-03 vas escriure demanarJson amb ErrorDeApi, AbortController, timeouts i reintents. A 07-04 vas lidiar amb la reconnexió del CanalTauler i amb el lot d'actualitzacions que arriba després. A 07-03 vas fer optimistic UI: pintar el canvi abans que el servidor el confirmi, i revertir-lo si falla.

La conclusió, que reprendràs a 10-03, és que guardar l'estat del servidor al mateix lloc que l'estat de la interfície és un error de categoria. Són problemes amb regles diferents i mereixen eines diferents. Per això existeixen TanStack Query, RTK Query, SWR o els resources d'Angular: no són «Redux però modern», són memòries cau d'estat remot amb invalidació, reintent i deduplicació. Veuràs el concepte a 10-02 i 10-03.

  1. El cicle de vida i per què existeix

Un tros d'interfície passa per tres moments, i hi ha feina que només es pot fer en cadascun:

stateDiagram-v2
  [*] --> Muntat: apareix a la pantalla
  Muntat --> Actualitzat: canvia l'estat o les props
  Actualitzat --> Actualitzat: torna a canviar
  Muntat --> Desmuntat: desapareix de la pantalla
  Actualitzat --> Desmuntat: desapareix de la pantalla
  Desmuntat --> [*]
Moment Què s'hi fa El teu equivalent avui
Muntar Demanar dades, subscriure's al WebSocket, engegar un IntersectionObserver, mesurar el DOM real El constructor de TaulerVista i el seu primer render()
Actualitzar Tornar a pintar; reaccionar a un canvi de props (per exemple, canvia l'id de la tasca mostrada) actualitzar() + reconciliar()
Desmuntar Apagar-ho tot: treure escoltes, cancel·lar peticions, netejar temporitzadors, tancar canals destruir() i l'AbortController de 09-03

El cicle de vida no és una curiositat dels frameworks: és la resposta al problema 4, el que et va costar una lliçó sencera. I mereix un matís important, perquè és on més gent s'equivoca.

Un framework et garanteix que es cridarà la funció de neteja, però no endevina què cal netejar. Si obres un setInterval en el muntatge i no retornes res a la neteja, l'interval continua viu exactament igual que en JavaScript pur. El que aporta el framework és el moment: un lloc garantit on posar l'apagada, invocat automàticament quan el component desapareix. Això converteix «recordar-se de cridar destruir() des del lloc correcte» en «escriure el return de la funció». És una millora enorme d'ergonomia, no una supressió del problema.

  1. Llibreria davant de framework: què significa «amb opinions»

La distinció clàssica es resumeix en qui crida qui:

  • Una llibreria és codi que crides tu. Tu dirigeixes el flux; la llibreria resol un problema concret quan l'hi demanes.
  • Un framework és codi que et crida a tu. Ell dirigeix el flux; tu omples els forats que ell defineix. És l'anomenada inversió de control.

A la pràctica la frontera és porosa, però la conseqüència sí que és nítida: un framework decideix coses per tu. I a aquestes decisions ja preses se les anomena «opinions».

Decisió React (llibreria de vistes) Angular (framework complet)
Encaminador Tries tu entre uns quants Inclòs i oficial
Peticions HTTP Tries tu (fetch, axios, TanStack Query…) HttpClient inclòs
Formularis Tries tu Dos sistemes inclosos
Estat global Tries tu (10-03) Serveis amb senyals, inclosos
Llenguatge JavaScript o TypeScript TypeScript, de fet obligatori
Estructura de carpetes La que vulguis La que genera la CLI
Cadena d'eines La muntes tu (o amb un meta-framework) La CLI ho fa tot

Cap columna no és millor. Són intercanvis diferents del mateix recurs escàs: decisions.

  • Moltes opinions: arrencada ràpida sense discutir, qualsevol projecte de l'empresa s'assembla als altres, la persona nova s'orienta en dos dies. A canvi, quan necessites sortir del camí marcat, costa.
  • Poques opinions: màxima llibertat, tries la millor eina per a cada peça. A canvi, cada equip munta la seva pròpia combinació, i aquesta combinació s'ha de documentar, mantenir i actualitzar. Se'n diu «fatiga de decisió» i és real: dos equips de la mateixa empresa fent servir React poden tenir projectes que no s'assemblen gens.

Una dada per calibrar el teu propi cas: Nómada Tasques és avui un projecte sense opinions i amb les teves. Tu vas decidir Vite, Jest, Cypress, ESLint, l'estructura de carpetes, el nom dels esdeveniments i el contracte de les vistes. Va ser una decisió bona i coherent. També et va portar quatre mòduls.

  1. El cost real d'adoptar un framework

Aquí és on gairebé tots els materials d'aprenentatge callen. Un framework no és gratis. Aquests són els cinc costos, amb xifres aproximades perquè et facis una idea de l'ordre de magnitud (els números concrets envelleixen; les proporcions no tant).

Cost 1 · Pes descarregat. Un motor de reconciliació o un sistema d'injecció de dependències són codi que l'usuari descarrega i executa abans de veure res. Els paquets base actuals, comprimits, van des d'uns pocs kilobytes en els frameworks compilats fins a diverses desenes en els més complets. La teva aplicació sencera pesa avui 58,3 kB en tres peticions. Per a una aplicació gran, sumar-hi el motor és irrellevant; per a un widget en una pàgina de màrqueting, pot duplicar el pes de la pàgina.

Cost 2 · Corba d'aprenentatge. No és la sintaxi, que s'aprèn en un dia. És el model mental: quan es torna a executar la teva funció, per què aquest efecte es dispara dues vegades, què és una dependència estable, per què això no s'actualitza. Compta entre dues setmanes i dos mesos fins a ser productiu de debò, i força més fins a depurar amb soltesa un problema de reactivitat.

Cost 3 · Cadena d'eines. Un framework rarament ve sol. Porta empaquetador, compilador, complements de l'editor, regles d'ESLint específiques, un entorn de proves propi i, molt sovint, TypeScript. Aquest conjunt s'ha d'instal·lar, configurar, actualitzar i arreglar quan es trenca. És temps que no es dedica al producte i que no es veu en cap demostració.

Cost 4 · Dependència. El codi escrit per a un framework no es pot dur a un altre sense reescriure'l. El teu Tauler, el teu demanarJson, les teves utilitats de dates i format són JavaScript pur: valen a qualsevol lloc, avui i d'aquí a deu anys. Un component escrit per a un framework concret val mentre aquell framework existeixi i mentre la seva API no canviï. Aquesta asimetria té una conseqüència de disseny molt pràctica que convé recordar: mantén la lògica de negoci fora del framework. Que la vista sigui del framework i el model sigui teu. Nómada Tasques ja està organitzada així, i no per casualitat.

Cost 5 · Rotació de l'ecosistema. Les biblioteques que orbiten un framework popular canvien de pressa: la recomanada per encaminar fa cinc anys pot estar abandonada avui. Actualitzar un projecte amb vint dependències d'aquest ecosistema no és un cap de setmana: és un projecte. Aquest cost és proporcional a la popularitat, cosa que és una ironia útil de tenir present.

  1. Quan NO fer servir un framework

Una taula de decisió honesta, del tipus que no acostuma a aparèixer a la portada de cap documentació.

Situació Framework? Per què
Pàgina institucional amb poca interactivitat (un menú, un formulari de contacte) No El cost de descàrrega i d'eines no compensa. HTML + una mica de JavaScript, o un generador de llocs estàtics
Widget que s'incrusta al web d'un tercer No, o web components Un framework arrossega el seu motor i pot xocar amb el que ja hi hagi a la pàgina. Un web component natiu és una frontera neta
Aplicació interna amb formularis, taules i permisos És exactament el cas per al qual es van dissenyar: molta interfície, molt estat, molta reutilització
Plafó de dades amb moltes vistes i navegació Encaminament, divisió de codi i components reutilitzables aporten des del primer dia
Equip d'una sola persona, projecte petit i estable Probablement no Els avantatges de convenció i contractació no hi apliquen; el cost sí
Projecte que ha de funcionar sense tocar-lo durant deu anys Amb molt de compte El JavaScript estàndard continuarà funcionant; una versió antiga d'un framework amb dependències sense mantenir és un deute amb interessos
Producte amb SEO crític i contingut majoritàriament estàtic Només amb renderitzat al servidor, o millor un enfocament d'illes Veuràs per què a 10-06
Interfície amb requisits extrems de mida (quioscos, IoT, mercats amb xarxa molt limitada) Compilat o res Cada kilobyte compta
Equip que ja en domina un Sí, aquell La productivitat de l'equip pesa més que qualsevol comparativa tècnica

I el senyal més fiable de tots, que pots aplicar avui mateix a Nómada Tasques: si estàs escrivint a mà, per segona vegada, una cosa que un framework et dona feta, és que el framework ja compensava. Quan vas escriure reconciliar eres al límit. Quan vas escriure la llista virtualitzada, ja l'havies creuat —tret que l'objectiu, com aquí, fos precisament aprendre com funciona per dins.

  1. El panorama actual per situar-se

Una taula breu per ubicar-te. No és una comparativa —aquesta és la lliçó 10-06—, és un mapa perquè els noms deixin de sonar a soroll.

Eina Què és Reactivitat Tret distintiu
React Llibreria de vistes DOM virtual (+ compilador emergent) Ecosistema més gran, màxima llibertat, més demanda laboral
Vue Framework progressiu Senyals + compilació S'adopta a poc a poc; ecosistema oficial coherent
Angular Plataforma completa Senyals (abans Zone.js) Tot inclòs, TypeScript, injecció de dependències
Svelte Compilador Compilació + runes El motor gairebé desapareix; molt poc codi escrit
Solid Llibreria Senyals de gra fi + compilació de JSX JSX amb rendiment de senyals; sense DOM virtual
Astro Meta-framework de contingut Cap per defecte Illes: HTML estàtic amb trossos interactius, del framework que vulguis
htmx Llibreria petita Cap El servidor retorna HTML; el client l'insereix. Torna l'estat al servidor
Web components Estàndard del navegador Cap d'inclosa Natius, sense dependències, duren el que duri la plataforma

Dues observacions sobre aquesta taula. La primera: les tres últimes files no són «alternatives menors», són enfocaments diferents del problema. Astro i htmx qüestionen la premissa que la interfície s'hagi de construir al navegador. Si la teva aplicació és sobretot contingut amb una mica d'interacció, aquesta premissa és cara i potser innecessària. Ho veuràs a 10-06.

La segona: aquest mòdul cobreix React, Vue i Angular perquè són els tres que concentren la majoria de l'ocupació, de la documentació i dels projectes existents, i perquè entre tots tres cobreixen els tres models de reactivitat i els dos extrems de l'eix llibreria-framework. Qui entengui els tres, entén els altres llegint-ne la documentació.

  1. Què fa aquest mòdul i què no fa

Un avís explícit, perquè afecta com has de llegir les cinc lliçons següents.

El Mòdul 11, el projecte final, es construeix en JavaScript pur. Amb Nómada Tasques tal com està: js/model/, js/dades/, js/vista/, les seves 124 proves de Jest, els seus tres recorreguts de Cypress, el seu Vite i el seu service worker. No hi ha canvi de rumb, no hi ha reescriptura, i no necessitaràs instal·lar React per acabar el curs.

Aleshores, per a què serveix aquest mòdul? Per tres raons concretes:

  1. Criteri. Treballaràs amb frameworks, gairebé amb tota seguretat. La diferència entre fer-los servir bé i fer-los servir per inèrcia és saber quin problema resolen. Aquesta és la raó principal.
  2. Perspectiva sobre el teu propi codi. Veure el teu reconciliar convertit en una propietat key, el teu destruir() en una funció de neteja i la teva memòria cau per versió en un computed il·lumina cap enrere el que has construït. S'entén millor el que és propi quan es veu anomenat per altres.
  3. Decisió informada. En acabar 10-06 hauràs vist la mateixa pantalla escrita de quatre maneres, amb les seves mètriques. Que el projecte final sigui JavaScript pur deixarà de ser el que saps fer per passar a ser el que has decidit, que no és el mateix.

I hi ha una raó pràctica més: aprendre un framework de debò requereix un curs propi. El que sí que pots fer en cinc lliçons és entendre els quatre enfocaments prou bé com per triar quin aprendre a fons, i per llegir codi aliè sense sentir-te perdut. Aquest és l'objectiu declarat.

Errors Habituals i Consells

Creure que un framework fa l'aplicació més ràpida. No és cert en general. Afegeix pes a l'arrencada i una capa de feina a cada actualització. El que fa és que sigui difícil escriure una interfície lenta per descuit, perquè la reconciliació evita el redibuixat complet que la majoria escriuria a mà. Tu ja no ets aquesta majoria: el teu render() triga 31 ms mesurats. Compara sempre contra el que tens, no contra un suposat.

Triar framework per comparatives de rendiment. Les diferències entre els grans, en aplicacions reals, són de mil·lisegons i queden sepultades per decisions que sí que importen: quantes dades demanes, quantes imatges carregues, com desplegues. Triar per benchmarks de llistes de 10.000 files és optimitzar una fila que no tindràs mai.

Adoptar-ne un per a un problema que no tens. Si la teva pàgina té un menú desplegable i un formulari, el framework és el problema, no la solució. La pregunta no és «és bo?», sinó «què m'està resolent avui?».

Ficar-ho tot a l'estat global des del primer dia. L'error més car i el més freqüent. Comença local, eleva quan dos germans ho necessitin, i fes servir un magatzem compartit només quan l'arbre t'hi obligui. Ho veuràs amb detall a 10-03.

Barrejar estat de servidor i estat d'interfície. Guardar la resposta de llistarTasques() al mateix lloc on guardes «el modal està obert» és ficar dos problemes amb regles diferents a la mateixa caixa. Conseqüència típica: dades obsoletes que ningú no sap quan refrescar.

Ficar la lògica de negoci als components. És la fallada que surt més cara a mitjà termini, perquè és la que fa irreversible l'elecció. Les teves regles R1–R10, el teu Tauler, el teu demanarJson: fora del framework. El component pinta i recull esdeveniments; el model decideix. Amb aquesta disciplina, canviar de framework és reescriure la vista; sense ella, és reescriure l'aplicació.

Creure que el cicle de vida neteja per tu. Et dona el lloc i el moment, no el contingut. Un setInterval sense cancel·lar continua sent una fuita amb framework i sense. Tot el que vas aprendre a 09-03 continua vigent.

Consell: aprèn-ne un de debò abans que tres per sobre. El model mental de la reactivitat es transfereix; la sintaxi, no tant. Qui en domina un llegeix els altres amb relativa comoditat. Qui ha fet el tutorial dels tres no en domina cap.

Consell: prototipa un dia amb el teu cas més difícil. No amb la llista de tasques de la documentació: amb el pitjor que tinguis. Per a Nómada Tasques seria la llista de 600 tasques amb filtre en temps real i actualitzacions per WebSocket. Un dia de prototip ensenya més que un mes de comparatives, i és el consell que reprendràs a 10-06.

Exercicis

Exercici 1 · El diagnòstic del teu propi codi

Obre mentalment (o de debò, si el tens escrit) js/vista/tauler-vista.js i js/vista/controlador.js. Per a cadascun dels vuit problemes de l'apartat 2, respon:

  1. El tens resolt, parcialment resolt o sense resoldre?
  2. Quantes línies del teu codi estan dedicades a resoldre'l?
  3. Si demà Nómada Tasques creixés fins a cinc pantalles i trenta tipus de component, aquest problema es tornaria més car, igual o més barat?

Escriu la resposta en una taula de tres columnes. L'objectiu no és l'exactitud dels números, sinó identificar quins problemes escalen malament.

Exercici 2 · D'imperatiu a declaratiu

Aquest codi imperatiu gestiona el filtre per responsable de Nómada Tasques. Reescriu-lo en estil declaratiu i explica què hi has guanyat.

// Imperatiu
function filtrarPerResponsable(nom) {
  const files = document.querySelectorAll('.tasca');
  let visibles = 0;
  let hores = 0;

  for (const fila of files) {
    const coincideix = nom === null || fila.dataset.responsable === nom;
    fila.hidden = !coincideix;
    if (coincideix) {
      visibles++;
      hores += Number(fila.dataset.hores);
    }
  }

  document.querySelector('#comptador').textContent = `${visibles} tasques`;
  document.querySelector('#hores').textContent = `${hores} h`;
  document.querySelector('#buit').hidden = visibles > 0;
  document.querySelector('#filtre-actiu').textContent = nom ?? 'Tots';
}

Pistes: fixa't en d'on surten les dades (del DOM o del model?), en quants llocs s'actualitzen i en què passa si una tasca canvia de responsable mentre el filtre està actiu.

Exercici 3 · La decisió honesta

Per a cadascun d'aquests tres projectes, decideix si faries servir un framework i quin dels tres enfocaments de l'apartat 20 encaixa millor. Justifica-ho amb almenys tres criteris d'aquesta lliçó i anomena explícitament el cost que estàs acceptant.

  • (a) El web públic de Taller Nómada: qui som, tarifes, galeria de fotos, formulari de contacte i un calendari de disponibilitat que s'actualitza cada hora. Ha de posicionar bé als cercadors. Una persona el manté tres hores al mes.
  • (b) Nómada Tasques convertida en producte per a vint tallers: cinc pantalles, permisos per rol, informes, edició col·laborativa en temps real, un equip de quatre persones i un horitzó de cinc anys.
  • (c) Un widget de «reserva la teva plaça» que Taller Nómada vol oferir a altres webs perquè l'incrustin amb dues línies de codi. Aquestes webs fan servir WordPress, Shopify i coses pitjors.

Solucions

Solució 1

La teva taula s'hauria d'assemblar força a aquesta. Els números de línies són aproximats i el que importa és l'última columna:

# Problema Estat al teu codi Línies aprox. Escala?
1 Sincronitzar estat i pantalla Resolt: cicle estat → render ~40 (render + actualitzar) , escala bé: el cicle no creix amb l'aplicació
2 Identitat de nodes Resolt: reconciliar amb data-id ~20 Malament: només val per a fills directes amb clau. Amb components imbricats caldria refer-ho
3 Rendiment Resolt: índex, memòria cau, lots, virtualització ~200 repartides Malament: cada pantalla nova necessita la seva pròpia virtualització i la seva pròpia memòria cau
4 Neteja Resolt: destruir() + AbortController ~30 Malament: depèn que algú cridi destruir(). Amb vint components, l'oblit és qüestió de temps
5 Composició Parcial: funcions que pinten, sense estat per instància Malament: no hi ha lloc natural per a l'estat local d'una targeta
6 Comunicació entre parts llunyanes Parcial: CustomEvent ~25 Malament: el flux només se segueix cercant cadenes pel projecte
7 Estructura/estil/comportament Sense resoldre: quatre fitxers per targeta Malament: quatre acoblaments sense comprovar, per component
8 Convencions Parcial: existeixen, no estan escrites Malament: cada persona nova les aprèn llegint codi

Conclusió de l'exercici: el problema 1 el tens resolt d'una manera que escala; els altres, no. Aquest és, en una frase, l'argument sencer a favor dels frameworks per a aplicacions que creixen — i l'argument en contra per a aplicacions que no creixeran.

Solució 2

El primer és diagnosticar els tres defectes del codi imperatiu:

  1. Les dades surten del DOM (fila.dataset.hores, fila.dataset.responsable). El DOM s'ha convertit en la base de dades, i és una mala base de dades: tot és text, no hi ha validació, i qualsevol cosa que toqui l'HTML corromp els càlculs.
  2. Hi ha quatre punts d'actualització (#comptador, #hores, #buit, #filtre-actiu) que cal recordar. Afegir una cinquena dada visible obliga a tocar aquesta funció i totes les altres que canviïn el filtre.
  3. Fa servir hidden en lloc de no renderitzar. Els nodes amagats continuen existint, ocupen memòria, apareixen a l'arbre d'accessibilitat si es fa malament i es troben amb Ctrl+F. Amb 600 tasques, hi ha 600 nodes per a 6 de visibles.

La versió declarativa:

// Declaratiu: el filtre és estat; la pantalla és una conseqüència
function filtrarPerResponsable(nom) {
  estat.filtres.responsable = nom;   // 1 · canvia l'estat
  render();                          // 2 · torna a descriure la pantalla
}

function render() {
  const visibles = tasquesVisibles(estat);      // del MODEL, no del DOM

  reconciliar(llista, visibles, (t) => t.id, (t, node) => pintarTargeta(t, node, AVUI));

  const hores = visibles.reduce((s, t) => s + t.horesEstimades, 0);

  $('#comptador').textContent      = `${visibles.length} tasques`;
  $('#hores').textContent          = `${hores} h`;
  $('#buit').hidden                = visibles.length > 0;
  $('#filtre-actiu').textContent   = estat.filtres.responsable ?? 'Tots';
}

Què hi has guanyat, concretament:

  • Una sola font de veritat. Els números surten dels objectes Tasca, amb els seus tipus correctes. Si l'HTML canvia, els càlculs continuen sent correctes.
  • Un sol lloc per actualitzar. Afegir «esforç ponderat» al plafó és una línia a render(), i funciona per a totes les accions existents, no només per al filtre.
  • Correcció davant de canvis concurrents. Si el CanalTauler de 07-04 canvia el responsable d'una tasca mentre el filtre està actiu, la versió imperativa deixa aquesta fila visible amb el responsable equivocat fins que algú torni a filtrar. La declarativa la recol·loca al següent render(), perquè no hi ha «memòria» del que ja s'ha calculat.
  • Menys nodes. reconciliar elimina de debò les targetes que no coincideixen, en lloc d'amagar-les: és la diferència entre 1.194 nodes i 7.812 que vas mesurar a 09-04.

El que hi has perdut, per ser justos: el filtre imperatiu només toca la propietat hidden de les files, mentre que el declaratiu recorre les tasques, filtra, ordena i reconcilia. Amb sis tasques és indistingible; el model declaratiu sempre fa més feina per actualització, i a canvi fa aquesta feina bé i escrita una sola vegada.

Solució 3

(a) El web públic de Taller Nómada: sense framework de client.

Criteris: la interactivitat és mínima (un menú, un formulari, un calendari que es refresca cada hora); el SEO és crític i l'HTML ha d'arribar fet des del servidor; el manteniment és de tres hores al mes, cosa que fa que la rotació de l'ecosistema (cost 5) sigui el risc dominant — ningú no hi serà per actualitzar vint dependències.

Enfocament adequat: un generador de llocs estàtics o un enfocament d'illes a l'estil d'Astro, amb HTML estàtic i un sol tros interactiu per al calendari. Una mica de JavaScript solt per al menú i el formulari ja n'hi ha prou.

Cost acceptat: si d'aquí a dos anys volen afegir una zona privada amb reserves i perfil d'usuari, caldrà replantejar-ho. És un cost raonable comparat amb mantenir una cadena d'eines per a un formulari de contacte.

(b) Nómada Tasques com a producte: framework, sens dubte.

Criteris: cinc pantalles amb navegació i divisió de codi; quatre persones que necessiten convencions compartides i contractes explícits (problemes 7 i 8); molt estat compartit entre pantalles (permisos, usuari, filtres); edició col·laborativa, que multiplica els problemes d'estat de servidor de l'apartat 15; i un horitzó de cinc anys, en què la contractació i la formació pesen tant com el codi.

Quin: qualsevol dels tres, i l'elecció s'ha de basar en l'equip i el mercat local abans que en la tècnica. Si l'equip ja en coneix un, aquell. Si ve d'altres plataformes amb injecció de dependències i tipus, Angular hi encaixa bé; si es valora la llibertat i un mercat laboral ampli, React; si es valora una corba suau i un ecosistema oficial coherent, Vue. És la decisió de 10-06.

Cost acceptat: dos mesos de corba d'aprenentatge repartits entre quatre persones, una cadena d'eines per mantenir i una dependència real. Es mitiga amb la disciplina de l'apartat 18: Tauler, regles R1–R10 i demanarJson es queden en JavaScript pur, fora del framework.

(c) El widget incrustable: web components natius, o JavaScript pur.

Criteris: s'executa en pàgines alienes el CSS i el JavaScript de les quals no controles; el pes importa molt perquè se suma a una pàgina que no és teva; l'aïllament d'estils és un requisit, no un luxe (el Shadow DOM el dona de debò); i la longevitat importa, perquè no pots demanar a cent webs que actualitzin el teu script.

Enfocament adequat: un custom element natiu amb Shadow DOM, sense dependències, o —si cal més interfície— un framework compilat que deixi un paquet molt petit i que es pugui empaquetar com a custom element.

Cost acceptat: escriure més a mà, sense les comoditats d'un framework, i resoldre tu els vuit problemes de l'apartat 2 dins del widget. És acceptable perquè el widget és petit i la seva superfície està acotada: precisament el cas on el framework no compensa.

Conclusió

Has fet el diagnòstic abans de mirar cap remei, que és l'única manera que els remeis s'entenguin. Coneixes els vuit problemes que apareixen en qualsevol interfície que mostri dades que canvien —sincronització, identitat dels nodes, rendiment del redibuixat, neteja, composició, comunicació entre parts llunyanes, acoblament entre estructura, estil i comportament, i convencions d'equip— i saps exactament quina és la teva solució per a cadascun, quant et va costar i quines d'elles escalen malament quan el projecte creix.

Entens el salt d'imperatiu a declaratiu no com una qüestió d'elegància sinó d'aritmètica: escriure n descripcions d'estat en lloc de n×(n−1) transicions. Tens l'equació UI = f(estat) amb les seves quatre conseqüències —la pantalla es torna raonable, comprovable, el DOM deixa de ser la font de la veritat, i apareix la factura de rendiment que cal pagar amb reconciliació—. I has vist el mateix marcatge d'una tasca com a feta escrit de les dues maneres: dotze passos fràgils davant de dos passos i una descripció.

Saps què significa reactivitat amb precisió —una dependència declarada que el sistema manté sol— i coneixes les tres famílies amb els seus mecanismes reals: el DOM virtual que compara dos arbres i per això necessita una key que és literalment el teu data-id, amb un cost proporcional a la mida de l'arbre; els senyals, que registren qui llegeix què i notifiquen en escriure, amb un cost proporcional al que canvia i un model mental més subtil on la reactivitat es perd en desestructurar; i la compilació, que resol el problema abans d'arribar al navegador, amb paquets mínims a canvi d'escriure en un llenguatge que només entén el seu compilador. Cap no és la correcta: cadascuna optimitza una cosa diferent, i totes tres s'estan barrejant.

Saps què és un component —plantilla, estat, comportament i estils en una unitat instanciable, componible i aïllada— i per què és millor unitat que els teus mòduls de vista/, amb els seus quatre acoblaments sense comprovar entre el <template>, el CSS, targeta.js i controlador.js, sense lloc natural per a l'estat per instància i sense avís quan reconciliar elimina un node. Distingeixes els quatre tipus d'estat —local, elevat, compartit i de servidor— amb la regla de començar sempre local i pujar només quan t'hi obliguin, i entens per què l'estat del servidor és un problema d'una altra naturalesa: no en som la font de veritat, pot quedar obsolet sense avisar-te i té estats intermedis que l'estat local no té. I saps que el cicle de vida existeix per resoldre el problema 4, donant-te el moment garantit per apagar el que vas encendre, sense endevinar per tu què cal apagar.

Tens clara la diferència entre llibreria i framework —qui crida qui— i què significa «amb opinions»: decisions ja preses, que estalvien discussions i costen llibertat. I, sobretot, coneixes el cost real d'adoptar-ne un: pes descarregat, dues setmanes a dos mesos de corba, una cadena d'eines per mantenir, una dependència que només es mitiga traient la lògica de negoci fora del framework, i una rotació de l'ecosistema proporcional a la popularitat. Amb la seva contrapartida: la taula de quan NO fer-ne servir un, amb el senyal més fiable de tots —si estàs escrivint a mà per segona vegada una cosa que un framework et dona feta, ja compensava.

Queda dit de forma explícita: el Mòdul 11 es construeix en JavaScript pur, amb Nómada Tasques tal com està, les seves 124 proves i els seus tres recorreguts de Cypress. Aquest mòdul no és un canvi de tecnologia sinó de perspectiva. A les quatre lliçons següents veuràs la mateixa pantalla —la llista de tasques amb el seu filtre per responsable i el seu botó de marcar com a feta— reimplementada quatre vegades, perquè la comparació sigui real i no una llista de característiques. Comencem per l'enfocament del DOM virtual i per la llibreria amb l'ecosistema més gran: Introducció a React.

Curs de JavaScript: De Principiant a Avançat

Mòdul 1: Introducció a JavaScript

Mòdul 2: Estructures de Control

Mòdul 3: Funcions

Mòdul 4: Objectes i Arrays

Mòdul 5: Objectes i Funcions Avançades

Mòdul 6: El Model d'Objectes del Document (DOM)

Mòdul 7: APIs del Navegador i Temes Avançats

Mòdul 8: Proves i Depuració

Mòdul 9: Rendiment i Optimització

Mòdul 10: Frameworks i Llibreries de JavaScript

Mòdul 11: Projecte Final

© Copyright 2026. Tots els drets reservats