Fins ara has vist la mateixa pantalla de Nómada Tasques escrita en JavaScript pur i en React, i les dues versions compartien un model: una funció que es torna a executar sencera i un motor que compara el resultat amb l'anterior. Vue canvia això d'arrel. La seva reactivitat és de gra fi: no compara arbres, sinó que registra quina expressió llegeix quina dada i actualitza només el que depèn del que ha canviat. És la família 2 de 10-01, en la seva versió més polida i accessible. I canvia també la unitat de treball: en lloc de repartir una targeta entre un <template>, un fitxer CSS i dos mòduls JavaScript, Vue l'agrupa sencera en un Component d'un Sol Fitxer, amb la seva plantilla, la seva lògica i els seus estils d'àmbit propi. En aquesta lliçó veuràs per què se'n diu framework progressiu i com s'afegeix tant a una pàgina existent com a un projecte complet; l'anatomia d'un component amb <template>, <script setup> i <style scoped>; tota la sintaxi de plantilla —interpolació, v-bind, v-on, v-if davant de v-show, v-for amb :key (el mateix data-id de sempre) i v-model explicat com a sucre i no com a màgia—; la reactivitat de debò, amb ref, reactive, per què existeix .value, com els proxies detecten les dependències i on es perd la reactivitat —l'error que més desconcerta—; computed comparat amb la teva memòria cau per versió de 09-02 i amb useMemo; watch i watchEffect amb el criteri per triar; el cicle de vida amb onMounted i onUnmounted, que torna a ser el teu destruir(); la comunicació amb props, emits, slots i provide/inject; els composables com a equivalent dels hooks personalitzats; Pinia en la seva versió mínima; i l'ecosistema oficial. Al final, la llista de tasques amb el seu filtre reimplementada en Vue i comparada amb les dues versions anteriors.
Contingut
- Què significa «framework progressiu»
- Afegir Vue a una pàgina existent
- Un projecte complet amb Vite
- Components d'un Sol Fitxer
- Per què agrupar plantilla, lògica i estils
- La plantilla declarativa: interpolació
v-bind: atributs dinàmicsv-on: esdevenimentsv-if,v-elseiv-showv-fori:keyv-model: la doble vinculació explicada com a sucre- Reactivitat:
refi per què existeix.value reactivei quan fer servir cadascun- Com detecten les dependències els proxies
- Els límits de la reactivitat: on es perd
computed: derivats amb memòria cauwatchiwatchEffect- Quan fer servir
computed,watchowatchEffect - El cicle de vida:
onMountedionUnmounted - Comunicació:
propsiemits slots: composició de contingutprovideiinject- Composables:
usarTauleriusarFiltreResponsable - Estat global amb Pinia
- L'ecosistema oficial i la fragmentació
- Nómada Tasques en Vue: la llista completa
- Comparació amb React i amb JavaScript pur
- Errors Habituals i Consells
- Exercicis
- Conclusió
- Què significa «framework progressiu»
Vue es defineix a si mateix com un framework progressiu, i aquesta etiqueta té un significat tècnic concret: es pot adoptar per capes, començant per la més petita, sense comprometre's amb les altres.
| Nivell d'adopció | Què fas servir | Què necessites |
|---|---|---|
| 1 · Millorar una pàgina existent | Vue des d'una etiqueta <script>, controlant un <div> |
Res: ni compilació, ni npm |
| 2 · Components en diverses pàgines | Vue + Components d'un Sol Fitxer | Vite |
| 3 · Aplicació d'una sola pàgina | + Vue Router | Vite |
| 4 · Aplicació gran | + Pinia, proves, TypeScript | La cadena completa |
| 5 · Renderitzat al servidor | + Nuxt | El meta-framework |
Aquesta gradació no existeix a Angular, que exigeix tota la plataforma des del principi, i a React només existeix a mitges (necessites compilació per a JSX des del primer minut, tret que hi renunciïs). És la raó per la qual Vue apareix tant en projectes que van començar sense framework i van créixer: es poden convertir per trossos.
Per a Nómada Tasques això significa una cosa molt concreta. Podries agafar l'aplicació actual —el teu index.html, el teu Tauler, les teves utilitats— i convertir només la llista de tasques en un component Vue, deixant la resta intacta. No és un exercici teòric: és una estratègia de migració real, i la reprendràs a 10-06.
- Afegir Vue a una pàgina existent
El nivell 1, sense cap eina:
<div id="tauler">
<label>
Responsable:
<select v-model="responsable">
<option :value="null">Tots</option>
<option v-for="n in responsables" :key="n" :value="n">{{ n }}</option>
</select>
</label>
<p>{{ visibles.length }} de {{ tasques.length }} tasques · {{ horesObertes }} h obertes</p>
<ul>
<li v-for="tasca in visibles" :key="tasca.id">
{{ tasca.titol }} — {{ tasca.responsable }} ({{ tasca.horesEstimades }} h)
</li>
</ul>
</div>
<script type="module">
import { createApp, ref, computed } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
import { BACKLOG } from './js/dades/backlog.js';
createApp({
setup() {
const tasques = ref(BACKLOG);
const responsable = ref(null);
const responsables = computed(() =>
[...new Set(tasques.value.map((t) => t.responsable).filter(Boolean))].sort()
);
const visibles = computed(() =>
responsable.value === null
? tasques.value
: tasques.value.filter((t) => t.responsable === responsable.value)
);
const horesObertes = computed(() =>
visibles.value.filter((t) => t.estat !== 'feta')
.reduce((s, t) => s + t.horesEstimades, 0)
);
return { tasques, responsable, responsables, visibles, horesObertes };
}
}).mount('#tauler');
</script>Val la pena aturar-se aquí, perquè aquest bloc ja conté gairebé tots els conceptes de la lliçó i funciona obrint l'HTML al navegador, sense npm install, sense compilar i sense configurar res. Fixa't en tres coses:
- La plantilla és HTML vàlid.
v-for,:valuei{{ }}són atributs i text; el navegador els ignora fins que Vue els interpreta. Això té una conseqüència pràctica important: el marcatge es pot escriure en qualsevol editor d'HTML, i una persona de disseny l'entén. - Vue només controla
#tauler. La resta de la pàgina continua sent teva. Podries tenir tres illes Vue independents a la mateixa pàgina al costat del teu JavaScript de sempre. BACKLOGés el teu mòdul de dades, sense adaptar. Vue no té opinió sobre d'on surten els objectes.
Per a producció es faria servir una versió compilada en lloc de la que interpreta plantilles al navegador, però el model mental és aquest.
- Un projecte complet amb Vite
Per a la resta de la lliçó farem servir el nivell 2 en endavant:
L'assistent pregunta si vols TypeScript, Vue Router, Pinia, proves amb Vitest i Playwright o Cypress. Aquesta llista és reveladora: tot l'ecosistema essencial és oficial, mantingut pel mateix equip i amb documentació integrada. És el contrari de la taula de decisions de 10-02.
El punt d'entrada:
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import './estils.css';
createApp(App).mount('#arrel');
- Components d'un Sol Fitxer
El fitxer .vue és la unitat de treball de Vue: plantilla, lògica i estils al mateix lloc.
<!-- src/components/TargetaTasca.vue -->
<script setup>
import { computed } from 'vue';
import { SEGUENT, ETIQUETA, estaVencuda } from '../domini/regles.js';
const props = defineProps({
tasca: { type: Object, required: true }
});
const emit = defineEmits(['avancar']);
const vencuda = computed(() => estaVencuda(props.tasca));
const seguent = computed(() => SEGUENT[props.tasca.estat]);
</script>
<template>
<li
class="tasca"
:class="[
`tasca--${tasca.prioritat}`,
{ 'tasca--feta': tasca.estat === 'feta', 'tasca--vencuda': vencuda }
]"
:data-id="tasca.id"
>
<h3 class="tasca__titol">{{ tasca.titol }}</h3>
<p class="tasca__meta">
{{ tasca.responsable ?? 'sense assignar' }} · {{ tasca.horesEstimades }} h
<span v-if="vencuda" class="tasca__avis"> · ⚠ vençuda</span>
</p>
<ul class="tasca__etiquetes">
<li v-for="etiqueta in tasca.etiquetes" :key="etiqueta" class="etiqueta">
{{ etiqueta }}
</li>
</ul>
<button
type="button"
:disabled="seguent === null"
:aria-label="`${ETIQUETA[tasca.estat]}: ${tasca.titol}`"
@click="emit('avancar', tasca.id)"
>
{{ ETIQUETA[tasca.estat] }}
</button>
</li>
</template>
<style scoped>
.tasca { border-left: 4px solid var(--vora); padding: 0.75rem; }
.tasca--alta { border-left-color: var(--vermell); }
.tasca--feta .tasca__titol { text-decoration: line-through; opacity: 0.6; }
.tasca--vencuda { background: var(--avis-suau); }
.tasca__etiquetes { list-style: none; display: flex; gap: 0.3rem; padding: 0; }
</style>Tres blocs, amb regles pròpies:
<script setup>és sucre de compilació. Tot el que declaris al nivell superior —variables, funcions, importacions— queda disponible a la plantilla automàticament, sensereturn. És la forma actual i recomanada d'escriure components Vue; veuràs codi antic ambexport default { setup() { … return { … } } }o ambdata()/methods(l'API d'opcions), que continuen funcionant però ja no són la primera opció.<template>conté HTML amb directives. Es compila a una funció de renderitzat durant la construcció.<style scoped>aplica els estils només a aquest component. El compilador afegeix un atribut únic als elements del component i un altre als selectors, de manera que el.tascad'aquí no pot afectar un.tascad'un altre lloc.
- Per què agrupar plantilla, lògica i estils
Aquest scoped mereix un paràgraf propi, perquè és on Vue resol el problema 7 de 10-01 de manera més directa que React.
Recorda la teva targeta a Nómada Tasques: el <template id="plantilla-tasca"> a index.html, les classes .tasca__* a css/estils.css, la lògica a js/vista/targeta.js i el comportament a js/vista/controlador.js. Quatre fitxers, quatre acoblaments i cap comprovat. Canviar el nom de .tasca__titol al CSS no produeix cap error: simplement la targeta es veu malament.
Amb un fitxer .vue, els quatre són a la vista a la mateixa pantalla. Canviar el nom d'una classe és una operació de cercar i reemplaçar dins d'un fitxer de 40 línies. I com que els estils tenen àmbit, no cal inventar convencions de noms (BEM i similars) per evitar col·lisions: l'aïllament és real, no una disciplina.
L'objecció clàssica —«barrejar HTML, CSS i JavaScript és mala pràctica»— confon dues coses. La separació d'interessos no és separació de tecnologies: és separació de responsabilitats. L'estructura, l'aspecte i el comportament d'una targeta són un sol interès, i repartir-los per tres fitxers no els desacobla, només els allunya. És exactament l'argument de l'apartat 12 de 10-01 sobre el component com a unitat correcta de reutilització.
- La plantilla declarativa: interpolació
El text dinàmic s'escriu amb dobles claus:
<h3>{{ tasca.titol }}</h3>
<p>{{ tasca.horesEstimades }} h · {{ tasca.horesEstimades * PESOS[tasca.prioritat] }} d'esforç</p>
<p>{{ tasca.responsable ?? 'sense assignar' }}</p>A dins hi va una expressió JavaScript, no una sentència — la mateixa regla que les claus de JSX. I el contingut s'escapa sempre: és textContent, no innerHTML, amb la mateixa protecció de 06-02.
Diferència amb React que convé fixar: a JSX escrius {} per a tot el que és dinàmic, inclosos els atributs. A Vue, les claus són només per a text; els atributs fan servir v-bind.
v-bind: atributs dinàmics
v-bind: atributs dinàmics<!-- Forma llarga i forma abreujada, idèntiques -->
<li v-bind:data-id="tasca.id">…</li>
<li :data-id="tasca.id">…</li>
<button :disabled="seguent === null">…</button>
<img :src="tasca.imatge" :alt="`Foto de ${tasca.titol}`">Dos casos especials que es fan servir constantment:
Classes. :class accepta una cadena, un objecte (clau = classe, valor = condició) o un array, i es combina amb l'atribut class estàtic:
<li
class="tasca"
:class="[
`tasca--${tasca.prioritat}`,
{ 'tasca--feta': tasca.estat === 'feta', 'tasca--vencuda': vencuda }
]"
>Compara-ho amb el que feies a pintarTargeta:
li.classList.remove(...CLASSES_PRIORITAT);
li.classList.add(`tasca--${tasca.prioritat}`);
li.classList.toggle('tasca--feta', tasca.estat === 'feta');
li.classList.toggle('tasca--vencuda', vencuda);Quatre instruccions imperatives que cal mantenir sincronitzades —inclòs el remove previ, que és fàcil oblidar— davant d'una descripció declarativa. I davant de React, on cal construir la cadena a mà amb plantilles o una utilitat, la sintaxi d'objecte de Vue és notablement més neta.
Estils. :style accepta un objecte amb propietats en camelCase:
v-on: esdeveniments
v-on: esdeveniments<!-- Forma llarga i abreujada -->
<button v-on:click="avancar(tasca.id)">Començar</button>
<button @click="avancar(tasca.id)">Començar</button>
<!-- Amb l'objecte d'esdeveniment -->
<select @change="enCanviar($event.target.value || null)">
<input @input="text = $event.target.value">La diferència visible amb React és que a la plantilla pots escriure la crida directament (avancar(tasca.id)), sense embolcallar-la en una funció fletxa. El compilador se n'encarrega: @click="avancar(tasca.id)" es converteix en un gestor que executa aquesta expressió. Amb @click="avancar" (sense parèntesis) es passa la funció i rep l'esdeveniment com a argument.
Vue hi afegeix modificadors, que resolen de manera declarativa coses que en JavaScript pur escrius a mà:
| Modificador | Equivalent en JavaScript pur |
|---|---|
@submit.prevent |
esdeveniment.preventDefault() |
@click.stop |
esdeveniment.stopPropagation() |
@click.self |
if (esdeveniment.target !== esdeveniment.currentTarget) return |
@click.once |
addEventListener(…, { once: true }) |
@scroll.passive |
addEventListener(…, { passive: true }) |
@keyup.enter |
if (esdeveniment.key === 'Enter') |
@keyup.esc |
if (esdeveniment.key === 'Escape') |
Tots ells et resultaran familiars de 06-03 i 06-07. Són dreceres, no funcionalitat nova, però eliminen una classe sencera d'oblits — sobretot el preventDefault dels formularis.
Nota sobre delegació: com a React, escriure @click a cadascun de 600 elements no crea 600 escoltes del DOM. El compilador genera gestors i Vue els gestiona de manera eficient. El patró closest('[data-accio]') de 06-04 continua sent vàlid si el necessites, però ja no és obligatori.
v-if, v-else i v-show
v-if, v-else i v-show<p v-if="visibles.length === 0" class="llista-buida">
Cap tasca no coincideix amb el filtre.
</p>
<ul v-else class="llista-tasques">
<li v-for="tasca in visibles" :key="tasca.id">…</li>
</ul>I la comparació que cal tenir clara:
v-if |
v-show |
|
|---|---|---|
| Què fa | Crea o destrueix l'element del DOM | El crea sempre i alterna display: none |
| Cost en alternar | Alt: muntar i desmuntar | Molt baix: una propietat CSS |
| Cost inicial si és fals | Zero: no es crea res | Es crea igualment |
| Cicle de vida del component | Es disparen onMounted/onUnmounted |
No es dispara res |
| Nodes al DOM | Només els visibles | Tots, sempre |
| Trobable amb Ctrl+F | No | Sí (és al DOM, amagat) |
| Quan fer-lo servir | Condició que canvia poc, o contingut car | Alternança molt freqüent d'una cosa lleugera |
Aquesta taula és la formalització d'una cosa que ja vas mesurar a 09-04: amagar 594 tasques amb hidden mantenia 7.812 nodes al DOM, mentre que eliminar-les en deixava 1.194. v-if és el segon, v-show el primer. Per al filtre de responsable, v-if és el correcte.
Vue permet a més agrupar sense afegir cap node:
<template v-if="carregant">
<p role="status">Carregant tasques…</p>
<div class="esquelet" aria-hidden="true"></div>
</template>És l'equivalent del fragment <>…</> de React.
v-for i :key
v-for i :key<ul class="llista-tasques">
<li v-for="tasca in visibles" :key="tasca.id" class="tasca">
{{ tasca.titol }}
</li>
</ul>
<!-- Amb índex -->
<li v-for="(tasca, index) in visibles" :key="tasca.id">
{{ index + 1 }}. {{ tasca.titol }}
</li>
<!-- Sobre un objecte -->
<li v-for="(quantitat, estat) in resumPerEstat" :key="estat">
{{ estat }}: {{ quantitat }}
</li>I aquí hi ha, per tercera vegada al mòdul, el mateix concepte: :key és el teu data-id de reconciliar (06-06) i la key de React (10-02). Vue el fa servir exactament igual: construeix un mapa de clau a node, aparella per clau i no per posició, reutilitza els que continuen vius i elimina els sobrants.
Les mateixes tres regles: estable, única entre germans, mai l'índex si la llista es mou. I un avantatge petit però útil de Vue: la regla d'ESLint del complement oficial marca com a error un v-for sense :key, mentre que a React només és un avís a la consola. És una decisió de framework «amb més opinions», en el sentit de 10-01.
Un detall que Vue fa millor que el DOM virtual pur: com que el compilador analitza la plantilla, sap quines parts són estàtiques i les salta per complet a les actualitzacions. Si el teu <li> té deu elements i només un depèn de dades, Vue n'actualitza un. React executa la funció sencera i compara els deu. És la barreja de famílies 2 i 3 de 10-01 en acció.
v-model: la doble vinculació explicada com a sucre
v-model: la doble vinculació explicada com a sucrev-model és la característica més famosa de Vue i la pitjor entesa. Es presenta com a «doble vinculació» i sona a màgia bidireccional. No ho és: és sucre sintàctic de dues coses que ja saps fer.
es compila, essencialment, en:
És a dir: un v-bind que baixa el valor i un v-on que el puja. Exactament el formulari controlat de 10-02, escrit en una línia en lloc de dues. No hi ha canal màgic ni observació del DOM: hi ha un atribut i un esdeveniment.
Vue adapta la parella segons l'element, que és el que estalvia feina de debò:
| Element | Propietat que enllaça | Esdeveniment que escolta |
|---|---|---|
<input type="text"> |
value |
input |
<textarea> |
value |
input |
<input type="checkbox"> |
checked |
change |
<input type="radio"> |
checked |
change |
<select> |
value |
change |
<select multiple> |
array de valors | change |
Amb modificadors útils:
<input v-model.trim="titol"> <!-- aplica .trim(): útil per a la R2 -->
<input v-model.number="hores"> <!-- converteix a número: sense això, "6" és cadena -->
<input v-model.lazy="cerca"> <!-- escolta `change` en lloc d'`input` -->v-model.number mereix una nota, perquè connecta amb 01-07: sense això, <input type="number"> retorna una cadena, i hores > 40 compararia text. És un dels errors més freqüents en validar la R3.
En components propis, v-model funciona sobre una prop i un esdeveniment:
<!-- Component fill: FiltreResponsable.vue -->
<script setup>
defineProps({ modelValue: { type: String, default: null } });
defineEmits(['update:modelValue']);
</script>
<template>
<select :value="modelValue" @change="$emit('update:modelValue', $event.target.value || null)">
<option value="">Tots</option>
<slot />
</select>
</template>Un altre cop: una prop que baixa i un esdeveniment que puja. El flux de dades continua sent unidireccional; v-model només posa nom al patró. Entendre-ho així evita el malentès de creure que el fill modifica l'estat del pare — no ho fa: li demana que el modifiqui.
- Reactivitat:
ref i per què existeix .value
ref i per què existeix .valueArribem al cor de Vue.
import { ref } from 'vue';
const responsable = ref(null);
console.log(responsable.value); // null
responsable.value = 'Iván'; // ← això dispara l'actualització de tot el que en depenguiLa pregunta que tothom fa al principi: per què .value?
La resposta és en una limitació del llenguatge que coneixes des de 01-05: JavaScript no permet interceptar l'assignació a una variable. No hi ha manera que responsable = 'Iván' executi codi. Es pot interceptar l'accés a una propietat d'un objecte —això és el que fan els getter/setter de 05-03 i els Proxy—, però no a una variable solta.
Així que Vue fa l'única cosa possible: ref(null) retorna un objecte amb una única propietat, value, el get i el set de la qual estan interceptats.
// El que és un ref, conceptualment (05-03: getters i setters)
function ref(inicial) {
let intern = inicial;
const subscriptors = new Set();
return {
get value() {
registrarDependencia(subscriptors); // ← qui llegeix, s'apunta
return intern;
},
set value(nou) {
if (Object.is(intern, nou)) return; // sense canvi, sense feina
intern = nou;
notificar(subscriptors); // ← qui va llegir, se n'assabenta
}
};
}Aquest .value que sembla una molèstia és, literalment, el preu que existeixi el mecanisme. I hi ha una compensació important: a la plantilla es desembolcalla sol. Dins de <template> escrius {{ responsable }}, no {{ responsable.value }}, perquè el compilador sap quines variables són refs i hi afegeix el .value per tu.
Comparació directa amb el que ja coneixes:
useState de React |
ref de Vue |
|
|---|---|---|
| Llegir | responsable |
responsable.value (a la plantilla: responsable) |
| Escriure | setResponsable('Iván') |
responsable.value = 'Iván' |
| Què provoca | Reexecutar el component sencer | Actualitzar només el que llegeix aquest ref |
| Es pot escriure des de fora del component | No | Sí: un ref és un objecte normal |
| Comparació de canvi | Object.is sobre el valor |
Object.is sobre el valor |
La tercera fila és la diferència fonamental. Canviar responsable.value no reexecuta App: reexecuta les expressions concretes que van llegir aquell ref. Aquest és el gra fi de 10-01.
I la quarta té una conseqüència pràctica molt útil: un ref es pot crear en un mòdul, exportar i modificar des de qualsevol lloc —inclòs el teu CanalTauler de 07-04 en rebre un missatge del WebSocket— sense estar dins de cap component.
reactive i quan fer servir cadascun
reactive i quan fer servir cadascunreactive és l'alternativa per a objectes: embolcalla l'objecte sencer en un Proxy i no necessita .value.
import { reactive } from 'vue';
const filtres = reactive({ responsable: null, text: '', ordre: 'prioritat' });
filtres.responsable = 'Iván'; // sense .value: reactiu directament
filtres.text = 'serigrafia';És més còmode, però té quatre limitacions reals:
| Limitació | Conseqüència |
|---|---|
Només funciona amb objectes, arrays, Map i Set |
Un número o una cadena no poden ser reactive |
| No es pot reemplaçar l'objecte sencer | filtres = {…} trenca la reactivitat; cal assignar propietat a propietat |
| Es perd en desestructurar | const { responsable } = filtres copia el valor, no el vincle |
| Es perd en passar una propietat primitiva a una funció | Es passa el valor, no la referència |
La recomanació actual, i la que segueix gairebé tot el codi Vue modern: fes servir ref per defecte. Funciona amb qualsevol tipus, es pot reemplaçar sencer, i el .value explícit fa visible on és la reactivitat. reactive queda per a objectes d'estat agrupat les propietats dels quals es manipulen una a una, i tot i així molts equips prefereixen un ref amb un objecte a dins:
const filtres = ref({ responsable: null, text: '' });
filtres.value.responsable = 'Iván'; // reactiu
filtres.value = { responsable: null, text: '' }; // reemplaçament complet, també reactiu
- Com detecten les dependències els proxies
Toca explicar el mecanisme de debò, perquè d'ell surten tots els comportaments —els bons i els estranys.
Vue manté, mentre executa un efecte reactiu (el render d'un component, un computed, un watchEffect), una referència a l'efecte actual. Quan durant aquella execució es llegeix una propietat reactiva, el Proxy intercepta la lectura i registra: «aquest efecte depèn d'aquesta propietat d'aquest objecte». Quan algú escriu aquella propietat, el proxy busca la llista d'efectes dependents i els torna a executar.
graph TD E["Efecte reactiu<br/>(render, computed, watchEffect)"] --> L["Llegeix tasca.estat"] L --> P["El Proxy intercepta el get"] P --> R["Registra: l'efecte depèn de<br/>(objecteTasca, 'estat')"] W["Algú escriu tasca.estat = 'feta'"] --> P2["El Proxy intercepta el set"] P2 --> B["Busca els efectes dependents<br/>de (objecteTasca, 'estat')"] B --> E2["Els torna a executar"]
Tres conseqüències que expliquen tota la resta:
1 · La detecció és automàtica i precisa. No declares dependències com a l'array d'useEffect: es descobreixen en executar. Això elimina d'una revolada la classe sencera d'errors «falta una dependència» i «aquesta dependència canvia sempre» de 10-02.
2 · La detecció és dinàmica. Si un computed té un if, les dependències de la branca no presa no es registren en aquesta execució. És correcte i és el que vols: si el resultat no depèn d'una dada, canviar aquella dada no ha de recalcular res.
3 · La detecció només funciona durant l'execució de l'efecte. Si llegeixes un valor reactiu dins d'un setTimeout, un .then() o un addEventListener, aquella lectura passa després, quan l'efecte ja ha acabat i el registre està tancat. No es registra res. Aquesta és la causa de la meitat dels «això no s'actualitza» de Vue.
// ❌ La lectura passa fora del context de seguiment
watchEffect(() => {
setTimeout(() => {
console.log(responsable.value); // no registra dependència
}, 100);
});
// ✅ La lectura passa a dins
watchEffect(() => {
const actual = responsable.value; // ← es registra aquí
setTimeout(() => console.log(actual), 100);
});
- Els límits de la reactivitat: on es perd
Aquest apartat és el que separa qui sap Vue de qui l'ha provat. La reactivitat es perd en quatre situacions, i totes tenen la mateixa causa: el vincle viu a la propietat, no al valor.
Límit 1 · Desestructurar un objecte reactiu.
const filtres = reactive({ responsable: null, text: '' });
// ❌ `responsable` és una còpia del valor, sense vincle
const { responsable } = filtres;
console.log(responsable); // null, i continuarà sent null per sempre
// ✅ toRefs manté el vincle convertint cada propietat en un ref
import { toRefs } from 'vue';
const { responsable: refResponsable } = toRefs(filtres);
console.log(refResponsable.value); // continua vinculattoRefs recorre l'objecte i crea, per a cada propietat, un ref el get/set del qual apunten a la propietat original. És l'eina específica per desestructurar sense trencar res, i es fa servir molt en retornar l'estat des d'un composable.
Límit 2 · Desestructurar les props.
const props = defineProps({ tasca: Object });
const { tasca } = props; // ❌ perd la reactivitat
// ✅ fer servir props.tasca directament, o toRefs(props)Amb un matís important: <script setup> té una transformació del compilador que fa reactiva la desestructuració de props escrita directament a defineProps. Fora d'aquest cas concret, la regla es manté.
Límit 3 · Passar un primitiu a una funció.
const comptador = ref(0);
function incrementar(valor) { valor++; } // ❌ rep una còpia del número
incrementar(comptador.value); // no fa res
function incrementarRef(ref) { ref.value++; } // ✅ rep l'objecte ref
incrementarRef(comptador);Aquest és el motiu profund pel qual ref és un objecte: els objectes es passen per referència i els primitius per valor, exactament com vas aprendre a 01-05 i 04-08. ref converteix un primitiu en una cosa que es pot passar sense perdre-la.
Límit 4 · Reemplaçar un objecte reactive sencer.
let filtres = reactive({ responsable: null });
filtres = reactive({ responsable: 'Iván' }); // ❌ la plantilla continua apuntant al primerLa comparació honesta amb React, que és la contrapartida anunciada a 10-01:
| React | Vue | |
|---|---|---|
| Declarar dependències | A mà, en un array | Automàtic, en llegir |
| Error típic | Dependència que falta o objecte que canvia sempre | Reactivitat perduda en desestructurar o en llegir fora de l'efecte |
| Quan es detecta l'error | ESLint avisa | En execució, sense avís: «no s'actualitza» |
| Model mental | Simple, verbós | Subtil, concís |
Cap dels dos no és «més fàcil». React t'obliga a declarar i per això ho pot comprovar amb una regla d'ESLint; Vue ho fa sol i per això la fallada és silenciosa. És un intercanvi real.
computed: derivats amb memòria cau
computed: derivats amb memòria caucomputed declara un valor derivat d'altres valors reactius:
import { ref, computed } from 'vue';
const tasques = ref(BACKLOG);
const responsable = ref(null);
const visibles = computed(() =>
responsable.value === null
? tasques.value
: tasques.value.filter((t) => t.responsable === responsable.value)
);
const horesObertes = computed(() =>
visibles.value.filter((t) => t.estat !== 'feta')
.reduce((s, t) => s + t.horesEstimades, 0)
);Quatre propietats:
- Es cacheja. El càlcul només s'executa si alguna dependència ha canviat. Llegir
visibles.valuecent vegades sense canvis executa el filtre una vegada. - Es compon.
horesObertesdepèn devisibles, que depèn detasquesiresponsable. Vue construeix el graf i propaga en l'ordre correcte, sense recalcular res dues vegades. - És mandrós. Si ningú no llegeix
horesObertes, no es calcula, encara que canviïn lestasques. - És de només lectura per defecte (se li pot definir un
set, però és rar i gairebé sempre indica un disseny millorable).
I aquí hi ha el paral·lelisme que tanca 09-02:
// La teva memòria cau per versió de 09-02
resum(avui = AVUI) {
if (this.#cacheResum?.versio === this.#versio && this.#cacheResum.avui === avui) {
return this.#cacheResum.valor;
}
const valor = this.#calcularResum(avui);
this.#cacheResum = { versio: this.#versio, avui, valor };
return valor;
}Vas escriure vint línies —comptador de versió, invalidació a cada mutació, comparació de la clau de memòria cau— i el risc permanent d'oblidar un #invalidar(). computed fa el mateix, amb la invalidació descoberta automàticament i sense possibilitat d'oblit.
Comparació amb useMemo de React, que és l'equivalent més proper:
useMemo (React) |
computed (Vue) |
|
|---|---|---|
| Dependències | Declarades a mà | Detectades automàticament |
| Risc d'error | Oblidar una dependència | Llegir fora del context de seguiment |
| Quan es fa servir | Com a optimització, quan mesures que fa mal | Com a forma natural d'escriure derivats |
| Es pot fer servir fora d'un component | No | Sí |
| Cost si sobra | Comparar l'array de dependències | Mínim |
La tercera fila és la diferència cultural entre els dos frameworks. A React, useMemo és l'excepció i calcular directament és la norma. A Vue, computed és la norma: qualsevol derivat es declara així, sense pensar en rendiment, perquè és la manera correcta d'expressar-ho. I per això a Vue gairebé mai no es veu el debat sobre memoïtzació excessiva de 10-02.
watch i watchEffect
watch i watchEffectTots dos fan efectes secundaris quan alguna cosa canvia. Són l'equivalent d'useEffect, amb el mateix advertiment: no serveixen per derivar valors —per a això hi ha computed.
watch observa una font concreta i rep el valor nou i l'anterior:
import { watch } from 'vue';
watch(responsable, (nou, anterior) => {
console.log(`Filtre: ${anterior} → ${nou}`);
actualitzarUrl({ responsable: nou }); // History API, 07-06
});
// Diverses fonts
watch([responsable, text], ([r, t]) => desarPreferencies({ r, t }));
// Un getter, per observar una propietat concreta
watch(() => props.tasca.estat, (nou) => {
if (nou === 'feta') anunciar(`${props.tasca.titol} completada`);
});
// Amb opcions
watch(responsable, carregarTasques, { immediate: true }); // s'executa també a l'inici
watch(filtres, desar, { deep: true }); // observa canvis imbricatswatchEffect executa la funció immediatament i descobreix sol les seves dependències, com un computed però per a efectes:
import { watchEffect } from 'vue';
watchEffect(() => {
document.title = `Nómada Tasques (${pendents.value})`; // depèn de `pendents`
});I tots dos accepten una neteja, que és un altre cop el teu destruir():
watchEffect((enNetejar) => {
const controlador = new AbortController();
llistarTasques({ responsable: responsable.value, signal: controlador.signal })
.then((dades) => { tasques.value = dades; })
.catch((error) => { if (error.name !== 'AbortError') fallada.value = error; });
enNetejar(() => controlador.abort()); // s'executa abans de la següent i en desmuntar
});Aquest bloc resol la condició de cursa de 10-02 amb el mateix AbortController de 07-03, i a Vue no cal declarar [responsable] en cap array: el watchEffect es va apuntar com a dependent en llegir responsable.value.
- Quan fer servir
computed, watch o watchEffect
computed, watch o watchEffectLa taula que evita el 90 % dels errors de reactivitat a Vue:
| Necessites… | Fes servir | Per què |
|---|---|---|
| Un valor derivat d'altres | computed |
Cachejat, mandrós, sense efectes |
| Reaccionar a un canvi concret sabent el valor anterior | watch |
Dona el nou i l'anterior, i és explícit |
| Sincronitzar amb alguna cosa externa fent servir diverses fonts | watchEffect |
Detecta les dependències sol |
| Executar alguna cosa només en muntar | onMounted |
És un moment del cicle, no un canvi |
Guardar un valor derivat en un ref amb watch |
Res: fes servir computed |
És l'antipatró equivalent al de 10-02 |
Aquest últim cas mereix veure's escrit, perquè és exactament l'error de l'apartat 16 de 10-02 traduït a Vue:
// ❌ MALAMENT: un watch per derivar
const horesObertes = ref(0);
watch(tasques, (t) => {
horesObertes.value = t.filter((x) => x.estat !== 'feta')
.reduce((s, x) => s + x.horesEstimades, 0);
}, { immediate: true });
// ✅ BÉ
const horesObertes = computed(() =>
tasques.value.filter((t) => t.estat !== 'feta')
.reduce((s, t) => s + t.horesEstimades, 0)
);Els mateixos tres defectes: dues fonts de veritat, un moment en què el valor està desfasat, i més codi. La regla és transferible entre frameworks: si és un valor, declara'l com a derivat; si és una acció, fes-la en un efecte.
- El cicle de vida:
onMounted i onUnmounted
onMounted i onUnmountedimport { onMounted, onUnmounted, onUpdated, ref } from 'vue';
const contenidor = ref(null); // un ref de plantilla: apuntarà al node real
onMounted(() => {
// El DOM ja existeix: aquí es pot mesurar, enfocar, observar
const observador = new IntersectionObserver(enEntrar); // 07-06
observador.observe(contenidor.value);
onUnmounted(() => observador.disconnect()); // ← la neteja
});Els moments principals, amb la seva equivalència:
| Vue | React | El teu Nómada Tasques |
|---|---|---|
onMounted |
useEffect(…, []) |
El constructor de TaulerVista + primer render() |
onUpdated |
Execució del component | actualitzar() |
onUnmounted |
return d'useEffect |
destruir() (09-03) |
onErrorCaptured |
Límit d'error | El try/catch de 02-05 al controlador |
La tercera fila torna a ser la important, i el missatge de 10-01 continua vigent: Vue et dona el moment garantit, no endevina què cal apagar. Un setInterval sense clearInterval a onUnmounted és exactament la mateixa fuita que en JavaScript pur.
Detall pràctic: un ref de plantilla (ref="contenidor" a l'HTML més const contenidor = ref(null) al script) és l'equivalent d'useRef de React i del teu $('.llista-tasques') de sempre. Val null fins que el component es munta, així que només es pot fer servir dins d'onMounted o després.
- Comunicació:
props i emits
props i emitsEl flux és el mateix que a React —dades cap avall, esdeveniments cap amunt— amb una diferència: a Vue es declara explícitament en les dues direccions.
<script setup>
// Cap avall: props, amb tipus, obligatorietat i validació
const props = defineProps({
tasca: {
type: Object,
required: true,
validator: (t) => typeof t.id === 'number' && typeof t.titol === 'string'
},
compacta: { type: Boolean, default: false }
});
// Cap amunt: esdeveniments declarats, amb validació opcional
const emit = defineEmits({
avancar: (id) => typeof id === 'number',
eliminar: (id) => typeof id === 'number'
});
function enPremerAvancar() {
emit('avancar', props.tasca.id);
}
</script><!-- El pare escolta amb @ -->
<TargetaTasca :tasca="tasca" @avancar="avancar" @eliminar="eliminar" />Diferències amb React que val la pena assenyalar:
| React | Vue | |
|---|---|---|
| Dades cap avall | Props (sense declarar) | defineProps amb tipus i validador |
| Esdeveniments cap amunt | Props de funció (enAvancar) |
defineEmits + emit('avancar') |
| Validació en desenvolupament | Només amb TypeScript | Inclosa en JavaScript |
| Distinció dades/esdeveniments | Cap: tot són props | Explícita a l'API |
La declaració d'emits és valuosa per una raó poc òbvia: documenta el contracte complet del component. Llegint defineProps i defineEmits saps tot el que entra i tot el que surt, sense cercar pel fitxer quines funcions es criden. A React aquesta informació està repartida per la signatura i cal reconstruir-la.
I la mateixa regla que a React: les props són de només lectura. Modificar props.tasca.estat des del fill és un error, i Vue avisa en desenvolupament. El fill emet; el pare decideix.
slots: composició de contingut
slots: composició de contingutEls slots són l'equivalent de children de React, i són més expressius perquè admeten diversos forats amb nom.
<!-- src/components/Plafo.vue -->
<template>
<section class="plafo">
<header class="plafo__capcalera">
<slot name="capcalera">
<h2>Sense títol</h2> <!-- contingut per defecte si no se n'aporta -->
</slot>
</header>
<div class="plafo__cos">
<slot /> <!-- el slot per defecte -->
</div>
<footer v-if="$slots.peu" class="plafo__peu">
<slot name="peu" /> <!-- només es pinta si el pare hi ha aportat alguna cosa -->
</footer>
</section>
</template><Plafo>
<template #capcalera>
<h2>Tasques obertes · {{ horesObertes }} h</h2>
</template>
<LlistaTasques :tasques="visibles" @avancar="avancar" />
<template #peu>
<button @click="netejarFiltres">Netejar filtres</button>
</template>
</Plafo>I els slots amb àmbit, que permeten al fill passar dades al contingut que aporta el pare:
<!-- LlistaTasques.vue: la llista controla el bucle, el pare decideix com es veu cada fila -->
<template>
<ul class="llista-tasques">
<li v-for="tasca in tasques" :key="tasca.id">
<slot name="fila" :tasca="tasca" :vencuda="estaVencuda(tasca)">
{{ tasca.titol }}
</slot>
</li>
</ul>
</template><LlistaTasques :tasques="visibles">
<template #fila="{ tasca, vencuda }">
<strong :class="{ vermell: vencuda }">{{ tasca.titol }}</strong>
<em>{{ tasca.responsable }}</em>
</template>
</LlistaTasques>És un patró molt potent: el component aporta la lògica (el bucle, les claus, el filtratge) i qui el fa servir aporta la presentació. A React l'equivalent es fa passant una funció com a prop, i funciona igual de bé; la diferència és de sintaxi, no de capacitat.
provide i inject
provide i injectL'equivalent de Context de React: posar un valor a disposició de tot el subarbre.
// En un avantpassat
import { provide, ref, readonly } from 'vue';
const tema = ref('clar');
provide('tema', readonly(tema)); // només lectura per als descendents
provide('alternarTema', () => { tema.value = tema.value === 'clar' ? 'fosc' : 'clar'; });// En qualsevol descendent, a qualsevol profunditat
import { inject } from 'vue';
const tema = inject('tema', 'clar'); // amb valor per defecte
const alternarTema = inject('alternarTema');I una diferència tècnica amb Context que importa: com que Vue té reactivitat de gra fi, proveir un ref no repinta tots els que fan inject. Només s'actualitzen els que realment llegeixen el seu .value. El problema de rendiment de Context que descrivia 10-03 —tots els consumidors es repinten quan canvia el valor— senzillament no existeix aquí.
readonly és una bona pràctica: els descendents llegeixen el valor i per canviar-lo criden la funció proveïda. Així el control de l'estat es queda on ha de ser.
- Composables:
usarTauler i usarFiltreResponsable
usarTauler i usarFiltreResponsableUn composable és una funció que fa servir l'API de reactivitat de Vue i retorna estat i operacions. És l'equivalent exacte dels hooks personalitzats de 10-02, amb una diferència important: no té regles d'ordre de crida, perquè Vue no identifica l'estat per posició sinó per objecte.
// src/composables/usarTauler.js
import { ref, computed } from 'vue';
import { SEGUENT, PESOS } from '../domini/regles.js';
/** Estat del tauler de Nómada Tasques i les seves operacions. */
export function usarTauler(tasquesInicials) {
const tasques = ref(tasquesInicials);
function avancar(id) {
const tasca = tasques.value.find((t) => t.id === id);
if (!tasca) return;
const desti = SEGUENT[tasca.estat];
if (desti === null) return; // R6: des de 'feta' no s'avança
tasca.estat = desti; // mutació directa: és reactiu
}
const resum = computed(() => {
const obertes = tasques.value.filter((t) => t.estat !== 'feta');
return {
total: tasques.value.length,
horesTotals: tasques.value.reduce((s, t) => s + t.horesEstimades, 0),
horesObertes: obertes.reduce((s, t) => s + t.horesEstimades, 0),
esforc: tasques.value.reduce((s, t) => s + t.horesEstimades * PESOS[t.prioritat], 0)
};
});
return { tasques, avancar, resum };
}// src/composables/usarFiltreResponsable.js
import { ref, computed } from 'vue';
export function usarFiltreResponsable(tasques) {
const responsable = ref(null);
const responsables = computed(() =>
[...new Set(tasques.value.map((t) => t.responsable).filter(Boolean))].sort()
);
const visibles = computed(() =>
responsable.value === null
? tasques.value
: tasques.value.filter((t) => t.responsable === responsable.value)
);
return { responsable, responsables, visibles };
}Compara avancar amb el seu equivalent de React a 10-02:
// React: immutable, obligatòriament
setTasques((actuals) => actuals.map((t) =>
t.id === id && SEGUENT[t.estat] === desti ? { ...t, estat: desti } : t
));
// Vue: mutació directa
tasca.estat = desti;Aquesta és la diferència pràctica més visible entre els dos frameworks. A React la immutabilitat és obligatòria perquè la detecció és per referència; a Vue el proxy detecta l'escriptura a la propietat, així que mutar és correcte i natural. Cap de les dues no és millor: React guanya en previsibilitat —l'estat no canvia mai sota els teus peus i les comparacions són trivials— i Vue guanya en concisió i en el fet que les 599 tasques que no canvien ni tan sols es toquen.
Una convenció sobre noms: l'ecosistema Vue fa servir useAlgunaCosa en anglès, igual que React. Aquí s'escriuen usarTauler i usarFiltreResponsable per coherència amb la resta del curs, però en codi real veuràs useTauler. El que importa és que sigui consistent.
- Estat global amb Pinia
Pinia és el magatzem oficial de Vue, i la seva versió mínima s'assembla sorprenentment a un composable:
// src/magatzems/tauler.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { SEGUENT } from '../domini/regles.js';
import { BACKLOG } from '../dades/backlog.js';
export const usarMagatzemTauler = defineStore('tauler', () => {
// ── estat ───────────────────────────────
const tasques = ref(BACKLOG);
const responsable = ref(null);
// ── derivats (equivalen als "getters") ──
const visibles = computed(() =>
responsable.value === null
? tasques.value
: tasques.value.filter((t) => t.responsable === responsable.value)
);
const horesObertes = computed(() =>
visibles.value.filter((t) => t.estat !== 'feta')
.reduce((s, t) => s + t.horesEstimades, 0)
);
// ── accions ─────────────────────────────
function filtrarPer(nom) { responsable.value = nom; }
function avancar(id) {
const tasca = tasques.value.find((t) => t.id === id);
if (tasca && SEGUENT[tasca.estat] !== null) tasca.estat = SEGUENT[tasca.estat];
}
return { tasques, responsable, visibles, horesObertes, filtrarPer, avancar };
});<script setup>
import { storeToRefs } from 'pinia';
import { usarMagatzemTauler } from '../magatzems/tauler.js';
const magatzem = usarMagatzemTauler();
const { visibles, horesObertes } = storeToRefs(magatzem); // ← storeToRefs, no desestructurar
const { avancar } = magatzem; // les funcions sí que es desestructuren
</script>storeToRefs és imprescindible i és una aplicació directa del límit 1 de l'apartat 15: desestructurar el magatzem copiaria els valors i trencaria la reactivitat. storeToRefs converteix l'estat i els derivats en refs vinculats; les accions són funcions normals i es poden desestructurar sense problema.
Comparat amb Redux (10-03):
| Redux Toolkit | Pinia | |
|---|---|---|
| Conceptes | Accions, reductors, selectors, middleware | Estat, derivats, accions |
| Immutabilitat | Obligatòria (amb Immer per escriure-la còmoda) | No cal: es muta |
| Traçabilitat i viatge en el temps | Excel·lent | Bona (DevTools de Vue) |
| Pes aproximat | ~13 kB | ~1,5 kB |
| Oficial del framework | No | Sí |
| Un magatzem o diversos | Un, dividit en slices | Diversos independents |
La conclusió de 10-03 es manté: comença sense magatzem. Un composable amb un ref en un mòdul ja és estat compartit —perquè un ref de mòdul és un objecte únic—, i sovint n'hi ha prou. Pinia hi afegeix DevTools, substitució en calent, la garantia d'una única instància i suport per a renderitzat al servidor. La diferència amb Redux és que l'esglaó és molt més baix: passar d'un composable a un magatzem Pinia són deu minuts.
- L'ecosistema oficial i la fragmentació
| Peça | Vue (oficial) | React (a triar) |
|---|---|---|
| Encaminament | Vue Router | React Router, TanStack Router, el del meta-framework |
| Estat global | Pinia | Redux Toolkit, Zustand, Jotai, Context |
| Empaquetament | Vite (creat pel mateix autor) | Vite, o el del meta-framework |
| Proves de components | Vue Test Utils + Vitest | Testing Library + Jest/Vitest |
| Renderitzat al servidor | Nuxt | Next.js, Remix, altres |
| Eines de desenvolupament | Vue DevTools | React DevTools |
| Llenguatge | JavaScript o TypeScript | JavaScript o TypeScript |
Aquesta columna de l'esquerra explica per què es diu que Vue té menys fragmentació, i mereix una anàlisi honesta en les dues direccions.
Avantatges reals: hi ha una resposta canònica a cada pregunta; la documentació oficial cobreix el conjunt i els exemples encaixen entre si; les actualitzacions estan coordinades; dos projectes Vue d'empreses diferents s'assemblen força, amb la qual cosa incorporar-se a un és més ràpid; i no cal dedicar una setmana a avaluar quatre llibreries d'encaminament.
Desavantatges reals: menys innovació des de fora, perquè hi ha menys incentiu per competir amb la solució oficial; menys opcions quan el teu cas és rar; i un ecosistema de tercers més petit en números absoluts —per a una necessitat molt específica és més probable trobar una llibreria React que una de Vue—.
És l'eix llibreria-framework de 10-01 amb Vue situat al mig: més opinions que React, menys que Angular. I també aquí convé desconfiar de les conclusions automàtiques: la fragmentació de React és un cost per a un equip petit i un avantatge per a un que necessita alguna cosa poc comuna.
- Nómada Tasques en Vue: la llista completa
La pantalla objectiu del mòdul, tercera versió. El domini és el mateix fitxer JavaScript de 10-02 i 10-03, sense tocar:
// src/domini/regles.js — idèntic a les tres versions
export const SEGUENT = Object.freeze({ pendent: 'en-curs', 'en-curs': 'feta', feta: null });
export const ETIQUETA = Object.freeze({ pendent: 'Començar', 'en-curs': 'Marcar feta', feta: 'Feta' });
export const PESOS = Object.freeze({ alta: 3, mitjana: 2, baixa: 1 });
export const AVUI = '2026-09-20';
export const estaVencuda = (t, avui = AVUI) => t.estat !== 'feta' && t.dataLimit < avui;El filtre:
<!-- src/components/FiltreResponsable.vue -->
<script setup>
defineProps({
responsables: { type: Array, required: true },
modelValue: { type: String, default: null }
});
defineEmits(['update:modelValue']);
</script>
<template>
<label class="filtre" for="filtre-responsable">
Responsable:
<select
id="filtre-responsable"
:value="modelValue ?? ''"
@change="$emit('update:modelValue', $event.target.value || null)"
>
<option value="">Tots</option>
<option v-for="nom in responsables" :key="nom" :value="nom">
{{ nom }}
</option>
</select>
</label>
</template>La targeta és la de l'apartat 4. I la llista:
<!-- src/components/LlistaTasques.vue -->
<script setup>
import TargetaTasca from './TargetaTasca.vue';
defineProps({ tasques: { type: Array, required: true } });
defineEmits(['avancar']);
</script>
<template>
<p v-if="tasques.length === 0" class="llista-buida">
Cap tasca no coincideix amb el filtre.
</p>
<ul v-else class="llista-tasques">
<TargetaTasca
v-for="tasca in tasques"
:key="tasca.id"
:tasca="tasca"
@avancar="$emit('avancar', $event)"
/>
</ul>
</template>
<style scoped>
.llista-tasques { list-style: none; padding: 0; display: grid; gap: 0.5rem; }
.llista-buida { color: var(--gris); font-style: italic; }
</style>I l'aplicació:
<!-- src/App.vue -->
<script setup>
import { usarTauler } from './composables/usarTauler.js';
import { usarFiltreResponsable } from './composables/usarFiltreResponsable.js';
import FiltreResponsable from './components/FiltreResponsable.vue';
import LlistaTasques from './components/LlistaTasques.vue';
import { BACKLOG } from './dades/backlog.js';
import { computed } from 'vue';
const { tasques, avancar, resum } = usarTauler(BACKLOG);
const { responsable, responsables, visibles } = usarFiltreResponsable(tasques);
const horesVisibles = computed(() =>
visibles.value.filter((t) => t.estat !== 'feta')
.reduce((s, t) => s + t.horesEstimades, 0)
);
</script>
<template>
<main class="tauler">
<header class="tauler__capcalera">
<h1>Nómada Tasques</h1>
<FiltreResponsable v-model="responsable" :responsables="responsables" />
<p class="tauler__resum">
{{ visibles.length }} de {{ resum.total }} tasques · {{ horesVisibles }} h obertes
</p>
</header>
<LlistaTasques :tasques="visibles" @avancar="avancar" />
</main>
</template>
<style scoped>
.tauler__capcalera { display: flex; gap: 1rem; align-items: baseline; flex-wrap: wrap; }
.tauler__resum { color: var(--gris); margin-left: auto; }
</style>Els números canònics, verificats: sense filtre, «6 de 6 tasques · 45 h obertes». Amb «Iván», «3 de 6 · 25 h». Amb «Lucía», «1 de 6 · 14 h». Amb «Marta», «2 de 6 · 6 h». I en prémer «Començar» a la tasca 6, avancar muta tasca.estat, el proxy avisa els computed que van llegir aquella propietat, i s'actualitzen només la targeta afectada i el resum. Les altres cinc targetes ni tan sols es tornen a executar.
- Comparació amb React i amb JavaScript pur
La taula de les tres versions de la mateixa pantalla:
| Concepte | JavaScript pur | React | Vue |
|---|---|---|---|
| Unitat | Mòdul vista/*.js + <template> + CSS |
Component de funció .jsx |
Component d'un sol fitxer .vue |
| Estils aïllats | No (classes globals) | No per defecte | Sí (scoped) |
| Estat | Objecte estat + render() manual |
useState |
ref |
| Derivats | Memòria cau per versió escrita a mà | useMemo (opcional, per optimitzar) |
computed (la forma natural) |
| Actualitzar | render() a mà |
Reexecutar el component | Reexecutar només el que llegeix la dada |
| Immutabilitat | Recomanada | Obligatòria | No cal: es muta |
| Identitat a les llistes | data-id + reconciliar() |
key |
:key |
| Neteja | destruir() invocat a mà |
return d'useEffect |
onUnmounted |
| Dependències d'efectes | — | Array declarat a mà | Detectades automàticament |
| Esdeveniments cap amunt | CustomEvent |
Prop de funció | emit declarat |
| Composició de contingut | <template> + <slot> de web components |
children |
slots amb nom i àmbit |
| Lògica reutilitzable | Mòduls i classes | Hooks (amb regles d'ordre) | Composables (sense regles d'ordre) |
I les mètriques de la mateixa pantalla:
| Mètrica | JavaScript pur | React | Vue |
|---|---|---|---|
| Línies de la vista | ~210 | ~130 | ~120 |
| Línies d'infraestructura pròpia | ~60 | 0 | 0 |
| Fitxers de la vista | 5 (dom, targeta, tauler-vista, controlador, <template>) |
4 .jsx + 2 hooks |
4 .vue + 2 composables |
| Dependències de producció | 0 | 2 | 1 |
| Pes del motor (comprimit, aprox.) | 0 | ~45 kB | ~35 kB |
| Fitxers a tocar per afegir una dada a la targeta | 2 | 1 | 1 (inclòs el seu estil) |
| Feina en canviar el filtre | Reconciliar la llista | Executar 6 components + comparar | Actualitzar només el que ha canviat |
I el balanç qualitatiu, sense declarar guanyadors:
Vue guanya en: concisió (menys cerimònia per al mateix), estils aïllats sense convencions de noms, reactivitat automàtica que elimina l'array de dependències, computed com a forma natural en lloc d'optimització, un ecosistema oficial coherent i una corba d'aprenentatge més suau per a qui ve d'HTML i CSS.
React guanya en: mida del mercat laboral i de l'ecosistema de tercers, un model mental més simple d'explicar (és una funció que es reexecuta), «tot és JavaScript» sense sintaxi de plantilla per aprendre, millor suport d'eines de tercers, i React Native per a mòbil natiu.
JavaScript pur guanya en: zero dependències, zero pes de motor, zero compilació obligatòria, control absolut i un codi que continuarà funcionant d'aquí a deu anys sense actualitzar res.
Les contrapartides de Vue, dites amb claredat: la reactivitat automàtica falla en silenci quan es perd (apartat 15), i la fallada és «no s'actualitza», que és més difícil de diagnosticar que un error; la sintaxi de plantilla és un llenguatge addicional que cal aprendre i que les eines genèriques no entenen sense complements; i el mercat laboral és més petit que el de React a la majoria de llocs, cosa que és un criteri legítim encara que no sigui tècnic.
Errors Habituals i Consells
Oblidar .value al script. L'error número u. A la plantilla es desembolcalla sol, al script no. if (responsable === 'Iván') compara un objecte ref amb una cadena i sempre és fals, sense error. La regla d'ESLint del complement oficial de Vue ho detecta: activa-la.
Desestructurar un objecte reactiu o un magatzem de Pinia. const { responsable } = filtres copia el valor i trenca el vincle. Fes servir toRefs per a objectes reactius i storeToRefs per a magatzems.
Llegir valors reactius fora del context de seguiment. Dins d'un setTimeout, un .then() o un gestor registrat a mà, la lectura no es registra. Llegeix el valor abans, al cos de l'efecte.
Fer servir watch per derivar valors. És el mateix antipatró que useEffect per derivar estat a React: dues fonts de veritat i un instant de desfasament. Si és un valor, és computed.
Confondre v-if amb v-show. v-show deixa els nodes al DOM: amb 600 tasques, 600 nodes amagats, trobables amb Ctrl+F, amb el cost de memòria i d'arbre d'accessibilitat que vas mesurar a 09-04. Per filtrar, v-if.
Fer servir l'índex com a :key. El mateix error que a React i que al teu reconciliar. Si la llista es filtra o es reordena, els nodes canvien de dada i es perden el focus i l'estat intern.
Oblidar .prevent als formularis. @submit sense .prevent recarrega la pàgina. És tan freqüent que Vue va fer el modificador precisament per això.
Oblidar .number als camps numèrics. <input type="number" v-model="hores"> guarda una cadena. La validació de la R3 (hores > 0 && hores <= 40) compararia text. Fes servir v-model.number.
Mutar props des del fill. Vue avisa en desenvolupament. El fill emet un esdeveniment; el pare decideix què fer. És el flux unidireccional, que v-model no trenca sinó que formalitza.
Creure que v-model és màgia bidireccional. És un v-bind més un v-on. Entendre-ho així evita esperar comportaments que no existeixen.
Consell: fes servir ref per defecte i reactive només quan ho justifiquis. ref funciona amb qualsevol tipus, es pot reemplaçar sencer, i el .value fa visible on és la reactivitat.
Consell: mantén el domini fora dels components. domini/regles.js és el mateix fitxer a les tres versions d'aquesta pantalla, i aquesta és la millor prova que la disciplina de 10-01 funciona: en canviar de framework només es reescriu la vista.
Consell: instal·la Vue DevTools des del primer dia. Permet inspeccionar l'arbre de components, veure el valor de cada ref i cada computed en temps real, i —molt útil— veure quines dependències té registrades cada efecte, que és la manera directa de diagnosticar una reactivitat perduda.
Exercicis
Exercici 1 · Caçar les reactivitats perdudes
Aquest component té quatre fallades de reactivitat. Troba-les, explica per què falla cadascuna i escriu la versió corregida.
<script setup>
import { ref, reactive, computed, watch } from 'vue';
import { BACKLOG } from '../dades/backlog.js';
const filtres = reactive({ responsable: null, text: '' });
const { responsable } = filtres;
const tasques = ref(BACKLOG);
const horesObertes = ref(0);
watch(tasques, (t) => {
horesObertes.value = t.filter((x) => x.estat !== 'feta')
.reduce((s, x) => s + x.horesEstimades, 0);
}, { immediate: true });
const visibles = computed(() => {
setTimeout(() => console.log(filtres.text), 0);
return responsable === null
? tasques.value
: tasques.value.filter((t) => t.responsable === responsable);
});
function netejar() {
filtres = { responsable: null, text: '' };
}
</script>
<template>
<p>{{ visibles.length }} tasques · {{ horesObertes }} h</p>
<button @click="netejar">Netejar</button>
</template>Exercici 2 · Un composable amb cicle de vida
Escriu un composable usarTasquesRemotes(responsable) que:
- Rebi un
refamb el responsable filtrat. - Carregui les tasques del servidor amb el teu
llistarTasquesde 07-02 cada vegada que canviï el responsable. - Exposi
tasques,carregantierrorcom a refs. - Cancel·li la petició anterior en llançar-ne una de nova i en desmuntar el component, evitant la condició de cursa de 10-02.
- Exposi també un
computedamb les hores obertes de les tasques carregades.
Després, explica per què aquesta versió no necessita cap array de dependències i quin error de React s'evita amb això.
Exercici 3 · La mateixa pantalla, tres vegades
Sense escriure codi nou, omple aquesta taula comparant el mateix canvi funcional a les tres versions de la pantalla que has vist al mòdul: afegir un distintiu que mostri l'esforç ponderat de cada tasca (horesEstimades × PESOS[prioritat]), amb fons vermell si supera 30.
| Pas | JavaScript pur | React | Vue |
|---|---|---|---|
| Fitxers que cal obrir | |||
| On va el càlcul | |||
| On va el marcatge | |||
| On va l'estil | |||
| Risc d'oblidar alguna cosa | |||
| Què es recalcula en canviar el filtre |
Després respon: a la versió de JavaScript pur, què passaria si afegissis el distintiu a pintarTargeta però oblidessis afegir-lo també al <template> d'index.html?
Solucions
Solució 1
Fallada 1 · const { responsable } = filtres (línia 6). Desestructurar un objecte reactive copia el valor primitiu null i perd el vincle. responsable valdrà null per sempre, així que visibles no filtrarà mai res encara que l'usuari canviï el <select>. És el límit 1 de l'apartat 15.
Fallada 2 · watch per derivar horesObertes. És l'antipatró de l'apartat 18: dues fonts de veritat, un instant de desfasament i més codi. A més, com que tasques és un ref a un array i el watch no és profund, mutar l'estat d'una tasca no dispara el watch: horesObertes es quedaria obsoleta en marcar una tasca com a feta, que és justament el cas d'ús principal.
Fallada 3 · Lectura dins d'un setTimeout al computed. La lectura de filtres.text passa després que el computed hagi acabat, fora del context de seguiment: no es registra com a dependència. A més, un computed no ha de tenir efectes secundaris: ha de ser una funció pura de les seves dependències, igual que els teus reductors de 10-03.
Fallada 4 · filtres = { … } a netejar. Reassignar un objecte reactive trenca el vincle amb la plantilla, que continua apuntant al proxy original. A més, filtres està declarat amb const, així que això llançaria un TypeError en temps d'execució (01-05).
Versió corregida:
<script setup>
import { ref, computed } from 'vue';
import { BACKLOG } from '../dades/backlog.js';
// ref per defecte: funciona amb qualsevol tipus i es pot reemplaçar sencer
const filtres = ref({ responsable: null, text: '' });
const tasques = ref(BACKLOG);
// Derivat: computed, no watch
const horesObertes = computed(() =>
tasques.value.filter((t) => t.estat !== 'feta')
.reduce((s, t) => s + t.horesEstimades, 0)
);
// Pur i amb totes les lectures dins del context de seguiment
const visibles = computed(() => {
const { responsable, text } = filtres.value; // lectura A DINS: sí que es registra
const t = text.trim().toLowerCase();
return tasques.value
.filter((x) => responsable === null || x.responsable === responsable)
.filter((x) => t === '' || x.titol.toLowerCase().includes(t));
});
function netejar() {
filtres.value = { responsable: null, text: '' }; // reemplaçament del contingut del ref
}
</script>
<template>
<p>{{ visibles.length }} tasques · {{ horesObertes }} h</p>
<button @click="netejar">Netejar</button>
</template>Nota sobre la desestructuració de la línia const { responsable, text } = filtres.value: aquí sí que és correcta, perquè passa dins del computed. La lectura de filtres.value registra la dependència en aquell moment; el que es desestructura després són valors ja llegits, usats immediatament. La regla no és «no desestructuris mai», sinó «no guardis valors desestructurats fora d'un context reactiu».
Solució 2
// src/composables/usarTasquesRemotes.js
import { ref, computed, watchEffect } from 'vue';
import { llistarTasques } from '../dades/api-tasques.js'; // el teu mòdul de 07-02
/**
* Carrega les tasques del servidor filtrades per responsable.
* @param {import('vue').Ref<string|null>} responsable
*/
export function usarTasquesRemotes(responsable) {
const tasques = ref([]);
const carregant = ref(false);
const error = ref(null);
watchEffect(async (enNetejar) => {
const controlador = new AbortController();
enNetejar(() => controlador.abort()); // ← abans de la següent execució i en desmuntar
carregant.value = true;
error.value = null;
try {
// La lectura de responsable.value registra la dependència AQUÍ, abans de l'await
tasques.value = await llistarTasques({
responsable: responsable.value,
signal: controlador.signal
});
} catch (fallada) {
if (fallada.name === 'AbortError') return; // cancel·lació esperada: no és un error
error.value = fallada; // ErrorDeApi de 07-03
} finally {
carregant.value = false;
}
});
const horesObertes = computed(() =>
tasques.value.filter((t) => t.estat !== 'feta')
.reduce((s, t) => s + t.horesEstimades, 0)
);
return { tasques, carregant, error, horesObertes };
}Ús:
<script setup>
import { ref } from 'vue';
import { usarTasquesRemotes } from './composables/usarTasquesRemotes.js';
const responsable = ref(null);
const { tasques, carregant, error, horesObertes } = usarTasquesRemotes(responsable);
</script>
<template>
<p v-if="carregant" role="status">Carregant tasques…</p>
<p v-else-if="error" role="alert">No s'han pogut carregar: {{ error.message }}</p>
<ul v-else :aria-busy="carregant">
<li v-for="t in tasques" :key="t.id">{{ t.titol }}</li>
</ul>
<p>{{ horesObertes }} h obertes</p>
</template>Per què no cal array de dependències. El watchEffect llegeix responsable.value durant la seva execució, i el proxy registra aquella lectura automàticament. Quan el valor canvia, l'efecte es torna a executar — després d'haver cridat la neteja, que avorta la petició en vol.
Quin error de React s'evita. Dos, en realitat:
- La dependència oblidada. A React, escriure
}, [])en lloc de}, [responsable])produeix un component que carrega una vegada i no s'actualitza mai. És tan freqüent que existeix una regla d'ESLint específica per a això. Aquí és impossible per construcció. - La dependència que canvia sempre. Si a React passessis un objecte
{ responsable }com a dependència, es recrearia a cada renderitzat i la petició es llançaria infinitament. A Vue les dependències són els valors reactius llegits, no referències comparades.
I un avís, per no vendre fum: hi ha un cas on el seguiment automàtic de Vue també falla. Les lectures que passen després d'un await no es registren, perquè el context de seguiment es tanca en suspendre's la funció. Per això el codi llegeix responsable.value abans de l'await, dins de la crida. Si necessitessis llegir un altre ref després de l'await, l'hauries de llegir abans i guardar-lo. És el mateix límit de l'apartat 15, en la seva forma més subtil.
Solució 3
| Pas | JavaScript pur | React | Vue |
|---|---|---|---|
| Fitxers que cal obrir | 3: index.html (<template>), js/vista/targeta.js, css/estils.css |
2: TargetaTasca.jsx, el CSS |
1: TargetaTasca.vue |
| On va el càlcul | A pintarTargeta, com a variable local |
Al cos del component, calculat al render | En un computed del <script setup> |
| On va el marcatge | Afegir un <span> al <template> i omplir-lo a pintarTargeta |
Al JSX, al costat de la resta | Al <template> del mateix fitxer |
| On va l'estil | Classe nova al CSS global, amb prefix per evitar col·lisions | Classe al CSS global, o mòdul CSS | Al <style scoped>, sense risc de col·lisió |
| Risc d'oblidar alguna cosa | Alt: tres fitxers i un acoblament per nom de classe que ningú no comprova | Baix | Molt baix: tot és a la mateixa pantalla |
| Què es recalcula en canviar el filtre | Es reconcilien les targetes visibles i es recalcula el distintiu de cadascuna | S'executen els 6 components; amb memo, només els que canvien |
Només els computed les dependències dels quals han canviat |
Què passa si oblides el <span> al <template> d'index.html. Depèn de com estigui escrit pintarTargeta. Si fa servir $('.tasca__esforc', li).textContent = …, el selector retorna null i la línia llança un TypeError: Cannot set properties of null en temps d'execució, en pintar la primera targeta: l'aplicació es trenca sencera. Si en canvi ho comprova abans (const distintiu = $('.tasca__esforc', li); if (distintiu) …), no passa res visible: el distintiu simplement no apareix, sense cap error, i la fallada només es detecta mirant la pantalla o amb una prova d'extrem a extrem (08-06).
Aquest és, amb nom i cognoms, l'acoblament sense comprovar del qual parlava l'apartat 13 de 10-01. A la versió Vue l'error és impossible: el marcatge i el codi que l'omple són el mateix fitxer, i el compilador de plantilles avisa si una variable no existeix.
Conclusió
Has vist Vue en la seva versió moderna —Composition API i <script setup>— i, sobretot, has vist el segon model de reactivitat de 10-01 funcionant de debò.
Saps què significa framework progressiu i ho has comprovat: els mateixos conceptes serveixen per millorar una pàgina existent amb una etiqueta <script> i sense compilar, o per muntar una aplicació completa amb Vite, Vue Router i Pinia. Coneixes els Components d'un Sol Fitxer amb els seus tres blocs i per què agrupar plantilla, lògica i estils no viola la separació d'interessos sinó que la respecta: l'estructura, l'aspecte i el comportament d'una targeta són un sol interès, i el <style scoped> converteix l'aïllament en una garantia del compilador en lloc d'una convenció de noms.
Domines la sintaxi de plantilla: la interpolació amb el seu escapament automàtic, v-bind amb les formes d'objecte per a classes i estils —quatre instruccions imperatives de classList reduïdes a una descripció—, v-on amb els seus modificadors que codifiquen el preventDefault i el stopPropagation de 06-03, la diferència entre v-if i v-show —crear i destruir davant de display: none, amb els 7.812 nodes davant dels 1.194 que vas mesurar a 09-04—, v-for amb :key, que per tercera vegada al mòdul és el teu data-id de 06-06, i v-model desmitificat: un v-bind que baixa el valor i un v-on que el puja, el formulari controlat de 10-02 escrit en una línia, amb .trim i .number que eviten errors reals a les regles R2 i R3.
Entens la reactivitat per dins. Saps per què existeix .value —JavaScript no permet interceptar l'assignació a una variable, només a una propietat— i que un ref és un objecte amb get/set interceptats, exactament el mecanisme de 05-03. Saps com els proxies registren les dependències durant l'execució d'un efecte, amb les seves tres conseqüències: detecció automàtica i precisa, detecció dinàmica que ignora les branques no preses, i detecció només durant l'execució — d'on surten els quatre límits on la reactivitat es perd: desestructurar un objecte reactiu, desestructurar props, passar un primitiu a una funció i reemplaçar un reactive sencer, amb toRefs i storeToRefs com a eines. I tens la comparació honesta: React t'obliga a declarar dependències i per això les pot comprovar amb ESLint; Vue les descobreix sola i per això la fallada és silenciosa.
Saps que computed és la teva memòria cau per versió de 09-02 amb la invalidació descoberta automàticament —vint línies i un risc d'oblit convertits en una línia sense risc—, i que a diferència d'useMemo no és una optimització sinó la forma natural d'escriure derivats. Distingeixes watch i watchEffect amb el criteri per triar entre els tres, i reconeixes l'antipatró de derivar amb watch com el mateix error que derivar amb useEffect. Coneixes el cicle de vida amb onMounted i onUnmounted, que torna a ser el teu destruir() amb la crida garantida però el contingut a càrrec teu.
Saps comunicar components amb props i emits declarats —un contracte complet i validat que es llegeix en dues línies—, compondre contingut amb slots amb nom i amb àmbit, i injectar valors amb provide/inject, que a diferència de Context no repinta tots els consumidors perquè la reactivitat és de gra fi. Extreus lògica en composables sense regles d'ordre de crida, i saps que la diferència pràctica més visible amb React és a avancar: on React exigeix un map immutable, Vue muta la propietat i el proxy fa la resta. Coneixes Pinia en la seva versió mínima —gairebé idèntica a un composable, amb storeToRefs com a requisit— i l'ecosistema oficial que explica la menor fragmentació de Vue, amb els seus avantatges i els seus costos.
I has vist la tercera versió de la mateixa pantalla, amb el mateix domini/regles.js sense tocar a les tres, ~120 línies, un sol fitxer per component amb els seus estils aïllats, i actualitzacions que toquen només el que ha canviat. Amb les contrapartides dites: la reactivitat perduda falla en silenci, la sintaxi de plantilla és un llenguatge més, i el mercat laboral és més petit.
Queden dos models per veure. El següent és el més diferent de tots: un framework que no és una llibreria ni un framework progressiu sinó una plataforma completa, amb TypeScript com a requisit de fet, injecció de dependències, una CLI que ho genera tot, observables per als fluxos asíncrons, i —des de fa poc— senyals molt semblants als ref que acabes d'aprendre. És l'extrem «amb opinions» de l'eix de 10-01: Conceptes Bàsics d'Angular.
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
