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
- El diagnòstic abans del remei
- Els vuit problemes de construir una interfície a mà
- La taula del diagnòstic: problema → la teva solució → com ho resol un framework
- Imperatiu davant de declaratiu
UI = f(estat): l'equació i les seves conseqüències- El mateix tros de tauler, escrit de les dues maneres
- Què significa «reactivitat» exactament
- Família 1: DOM virtual i reconciliació
- Família 2: reactivitat de gra fi amb senyals
- Família 3: compilació en temps de construcció
- Les tres famílies en una taula
- El component: plantilla, estat, comportament i estils
- Per què el component és millor unitat que els teus mòduls de
vista/ - Estat: local, elevat, compartit i de servidor
- Per què l'estat del servidor és un problema diferent
- El cicle de vida i per què existeix
- Llibreria davant de framework: què significa «amb opinions»
- El cost real d'adoptar un framework
- Quan NO fer servir un framework
- El panorama actual per situar-se
- Què fa aquest mòdul i què no fa
- Errors Habituals i Consells
- Exercicis
- Conclusió
- 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.
- 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.
- 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.
- 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ò:
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
UI = f(estat): l'equació i les seves conseqüències
UI = f(estat): l'equació i les seves conseqüènciesLa forma condensada de tot això anterior és una equació que veuràs a la documentació de gairebé tots els frameworks moderns:
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:
- 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.
- 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. - 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. - Torna el problema del rendiment. Si
fprodueix 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.
- 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
- 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:
- Estat → estat derivat: si canvien les tasques, canvien les hores obertes, el nombre de pendents i la llista filtrada.
- 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.
- 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.memoi 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.
- 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
setTimeoutno 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,
useMemoté menys equivalents necessaris al món dels senyals.
- 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.
- 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) |
Sí |
| 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ó.
- 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é:
- 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 undataseto en unMapextern. - 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. - Aïlla. Els estils d'àmbit propi impedeixen que la classe
.titolde la targeta afecti la.titolde 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.
- Per què el component és millor unitat que els teus mòduls de
vista/
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 atributQuatre 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.
- 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?
- 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.
- 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.
- 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.
- 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.
- 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í | É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ó | Sí | 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.
- 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ó.
- 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:
- 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.
- Perspectiva sobre el teu propi codi. Veure el teu
reconciliarconvertit en una propietatkey, el teudestruir()en una funció de neteja i la teva memòria cau per versió en uncomputedil·lumina cap enrere el que has construït. S'entén millor el que és propi quan es veu anomenat per altres. - 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:
- El tens resolt, parcialment resolt o sense resoldre?
- Quantes línies del teu codi estan dedicades a resoldre'l?
- 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) |
Sí, 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:
- 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. - 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. - Fa servir
hiddenen 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
CanalTaulerde 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üentrender(), perquè no hi ha «memòria» del que ja s'ha calculat. - Menys nodes.
reconciliarelimina 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
- Què és JavaScript?
- Configuració del teu Entorn de Desenvolupament
- El teu Primer Programa en JavaScript
- Sintaxi i Conceptes Bàsics de JavaScript
- Variables i Tipus de Dades
- Operadors Bàsics
- Conversió de Tipus i Comparacions
- El Projecte del Curs: Nómada Tasques
Mòdul 2: Estructures de Control
- Sentències Condicionals
- Bucles: for, while, do-while
- Sentències Switch
- Control del Flux: break, continue i Bucles Imbricats
- Gestió d'Errors amb try-catch
Mòdul 3: Funcions
- Definició i Crida de Funcions
- Expressions de Funció i Funcions Fletxa
- Paràmetres i Valors de Retorn
- Àmbit i Closures
- Hoisting i el Context d'Execució
- Funcions d'Ordre Superior
- Recursivitat
Mòdul 4: Objectes i Arrays
- Introducció als Objectes
- Mètodes d'Objecte i la Paraula Clau
this - Arrays: Conceptes Bàsics i Mètodes
- Iteració sobre Arrays
- Cercar, Ordenar i Agregar Dades: find, sort i reduce
- Desestructuració d'Arrays
- Desestructuració d'Objectes, Spread i Rest
- JSON i Còpies d'Objectes
Mòdul 5: Objectes i Funcions Avançades
- Prototips i Herència
- Classes i Programació Orientada a Objectes
- Encapsulació: Getters, Setters i Camps Privats
- Mòduls i Importació/Exportació
- JavaScript Asíncron: Callbacks
- Promeses i Async/Await
- El Bucle d'Esdeveniments i la Cua de Microtasques
- Iteradors i Generadors
Mòdul 6: El Model d'Objectes del Document (DOM)
- Introducció al DOM
- Selecció i Manipulació d'Elements del DOM
- Gestió d'Esdeveniments
- Propagació, Delegació i Esdeveniments Personalitzats
- Creació i Eliminació d'Elements del DOM
- Renderitzat de Llistes i Plantilles HTML
- Gestió i Validació de Formularis
Mòdul 7: APIs del Navegador i Temes Avançats
- Emmagatzematge Local i de Sessió
- Fetch API i AJAX
- Peticions Robustes: Errors, Timeouts i AbortController
- WebSockets
- Service Workers i Aplicacions Web Progressives (PWAs)
- APIs del Navegador Essencials
- Introducció a WebAssembly
Mòdul 8: Proves i Depuració
- Depuració de JavaScript
- Qualitat de Codi: ESLint, Prettier i Convencions
- Proves Unitàries amb Jest
- Dobles de Prova: Mocks, Stubs i Spies
- Proves d'Integració
- Proves d'Extrem a Extrem amb Cypress
Mòdul 9: Rendiment i Optimització
- Mesurar Abans d'Optimitzar: DevTools i Web Vitals
- Optimització del Rendiment de JavaScript
- Gestió de Memòria
- Manipulació Eficient del DOM
- Càrrega Diferida i Divisió de Codi
Mòdul 10: Frameworks i Llibreries de JavaScript
- Per Què Existeixen els Frameworks
- Introducció a React
- Gestió d'Estat amb Redux
- Conceptes Bàsics de Vue.js
- Conceptes Bàsics d'Angular
- Triar el Framework Adequat
