Tot aquest mòdul s'ha recolzat en una regla que fins ara s'ha enunciat sense poder aplicar-la del tot: no optimitzis el que no has mesurat. S'ha parlat de renders que sobren, de props que canvien d'identitat, de càlculs de 40 ms i de paquets de 271 KB, i cadascuna d'aquestes afirmacions era, en realitat, una promesa: «això es pot comprovar». Aquesta lliçó és on es cobra la promesa. El React DevTools Profiler grava el que passa dins de React durant una interacció i respon amb precisió a les tres preguntes que converteixen una sospita en un diagnòstic: quins components s'han renderitzat, quant ha trigat cadascun i —la més valuosa— per què. Amb aquesta última resposta, «crec que la llista es repinta de més» deixa de ser una intuïció i passa a ser «TargetaBicicleta s'ha renderitzat 2.000 vegades en un commit de 214 ms perquè ha canviat la prop alReservar». La diferència entre les dues frases és la diferència entre tocar codi a cegues i arreglar un problema. Al final de la lliçó recorreràs el cas complet de CicloUrbano —mesurar, diagnosticar, corregir, tornar a mesurar— i sabràs també el més difícil de tot: quan parar.
Contingut
- Instal·lar React DevTools i què aporta cada pestanya
- Per què es mesura en un build de producció
- Anatomia del Profiler: gravar una interacció
- Commits, gràfic de flames i gràfic ordenat
- Els números: durada del render, temps propi i nombre de renders
- Per què s'ha repintat això: les quatre causes
- Ressaltar actualitzacions al navegador
- Cas d'estudi: el cercador de CicloUrbano, abans i després
- L'API
<Profiler>en codi - Complements fora de React
- Quan parar d'optimitzar
- Instal·lar React DevTools i què aporta cada pestanya
React DevTools és una extensió oficial disponible per a Chrome, Edge i Firefox, i també com a aplicació independent (npx react-devtools) per depurar React Native o pàgines servides fora del navegador d'escriptori.
Un cop instal·lada, en obrir les eines del navegador en una pàgina amb React apareixen dues pestanyes noves:
| Pestanya | Per a què serveix | Quan la fas servir |
|---|---|---|
| ⚛️ Components | Arbre de components amb els seus noms reals, props, estat, hooks i contextos. Permet editar-los en viu | Entendre l'estructura, inspeccionar un valor, comprovar quin context arriba a un component |
| ⚛️ Profiler | Gravació de renders amb temps i causes | Mesurar el rendiment: el contingut d'aquesta lliçó |
La icona de React a la barra del navegador també dona informació immediata sense obrir res: si té color, la pàgina usa React; i el seu to indica si està en mode de desenvolupament (vermell) o producció (blau). Aquest detall importa més del que sembla, i l'apartat següent explica per què.
Dos ajustos de la pestanya Components que convé conèixer abans de perfilar:
Highlight updates when components render: dibuixa un requadre al voltant de cada component que es renderitza. És el diagnòstic visual més ràpid que existeix (apartat 7).- Insígnia ✨ Memo ✨ al costat del nom d'un component: indica que el React Compiler l'ha optimitzat (08-03). Si el teu component crític no la té en un projecte compilat, és que el compilador l'ha omès.
- Per què es mesura en un build de producció
Aquesta és la instrucció més incomplerta del mòdul, i la que invalida més mesuraments.
npm run build # empaqueta amb Vite en mode produccio
npm run preview # serveix dist/ a http://localhost:4173Què distorsiona el mode de desenvolupament, punt per punt:
| Factor del mode desenvolupament | Efecte en els mesuraments |
|---|---|
StrictMode executa cada component dues vegades |
Els temps de render es dupliquen. Un component de 4 ms apareix com a 8 |
| Avisos i comprovacions de React | React valida props, claus i regles de hooks a cada render: cost que no existeix en producció |
| Codi sense minificar | Més codi a analitzar i funcions sense optimitzar pel motor de JavaScript |
| Mòduls sense empaquetar | Vite serveix cada fitxer per separat: centenars de peticions que en producció són una |
| Mapes de fonts (source maps) | Memòria i feina addicionals del navegador |
| Recàrrega en calent (HMR) | Instrumentació permanent a l'arbre de components |
| La pròpia instrumentació del Profiler | Afegeix cost mesurable, fins i tot en producció |
Les conseqüències pràctiques:
- Els temps absoluts del mode desenvolupament no serveixen. Un commit de 214 ms en desenvolupament pot ser de 60 ms en producció.
- Les proporcions sí que serveixen. Si en desenvolupament
TargetaBicicletaconsumeix el 80 % del commit, en producció també serà el problema. Per això desenvolupar amb el Profiler obert és útil per localitzar, encara que les xifres finals calgui prendre-les en un build. - Les conclusions del tipus «s'ha repintat N vegades» sí que són vàlides en desenvolupament, restant l'efecte de
StrictMode.
Un detall tècnic important: el build de producció estàndard de React elimina la instrumentació del Profiler. Per perfilar en producció cal construir amb el perfilat activat:
// vite.config.js — nomes per a builds de mesurament, no per desplegar
export default defineConfig({
resolve: {
alias: {
'react-dom/client': 'react-dom/profiling'
}
},
build: {
minify: 'terser',
terserOptions: { keep_fnames: true, keep_classnames: true } // noms llegibles
}
});keep_fnames mereix un comentari: sense ell, la minificació reanomena les funcions i el Profiler mostra t, e, n en lloc de TargetaBicicleta. Amb ell, el paquet pesa una mica més però el gràfic de flames és llegible. És una configuració de mesurament, no de desplegament.
Flux recomanat: localitza en desenvolupament (ràpid, amb recàrrega en calent) i confirma i quantifica en un build de producció amb perfilat. No tanquis mai un assumpte amb xifres de desenvolupament.
- Anatomia del Profiler: gravar una interacció
El cicle de gravació té cinc passos, i el segon és el que gairebé tothom es salta:
- Obre la pestanya Profiler.
- Prepara l'estat inicial: navega fins a la pantalla, espera que les dades hagin carregat, deixa la interfície quieta. Si graves la càrrega inicial junt amb la interacció, no podràs distingir una de l'altra.
- Prem el botó gravar (cercle blau).
- Fes una sola cosa: escriu cinc lletres al cercador. Res més.
- Atura la gravació i analitza.
Abans de gravar, activa aquests dos ajustos a l'engranatge ⚙ del Profiler:
| Ajust | Què fa | Recomanació |
|---|---|---|
| Record why each component rendered | Anota la causa de cada render | ✅ Sempre activat: és la meitat del valor de l'eina |
| Hide commits below _ ms | Amaga els commits trivials | Útil amb 1–2 ms per no perdre's en el soroll |
Highlight updates when components render |
Requadres visuals a la pàgina | Activar per al diagnòstic ràpid, desactivar en mesurar temps |
Un principi que estalvia molt de temps: una gravació, una interacció. Gravar «obro l'aplicació, navego, filtro, escric i reservo» produeix un registre impossible d'interpretar. Gravacions curtes i amb una hipòtesi concreta.
- Commits, gràfic de flames i gràfic ordenat
La línia temporal de commits
A la part superior apareix una sèrie de barres: cadascuna és un commit, és a dir, un cop que React ha aplicat canvis al DOM. Recorda el cicle de 01-05: render → diferències → commit. El Profiler mesura des que React comença a renderitzar fins que acaba d'aplicar els canvis.
- L'alçada de cada barra és la durada d'aquest commit.
- El color va de gris (ràpid) a groc (lent). El groc crida l'atenció, però el criteri real són els mil·lisegons.
- Prement una barra s'examina aquest commit concret.
Cinc pulsacions de tecla haurien de produir aproximadament cinc commits. Si en veus quinze, ja tens una dada: alguna cosa està provocant renders en cascada.
Gràfic de flames (flamegraph)
És la vista per defecte i mostra l'arbre de components d'aquest commit.
- Amplada = temps que aquest component i els seus descendents han trigat a renderitzar-se.
- Posició vertical = profunditat a l'arbre: els fills sota els pares.
- Color: gris = no s'ha renderitzat en aquest commit; de groc a verd = s'ha renderitzat, amb el groc indicant més temps.
El que cal buscar: barres amples i grogues, i sobretot barres de color on esperaves gris. Un component que no s'hauria d'haver renderitzat i apareix acolorit és exactament la troballa que es busca.
Gràfic ordenat (ranked)
La mateixa informació ordenada de major a menor temps, sense l'estructura de l'arbre. És la vista per respondre «què m'està costant el temps?» en dos segons. El gràfic de flames respon «per què està passant?».
flowchart TD
A["Gravar la interaccio"] --> B["Linia temporal:<br/>quants commits i de quina durada"]
B --> C{"Hi ha commits<br/>per sobre de 16 ms?"}
C -->|No| Z["No hi ha problema mesurable aqui"]
C -->|Si| D["Vista Ranked:<br/>QUIN component consumeix el temps"]
D --> E["Vista Flamegraph:<br/>ON esta a l'arbre"]
E --> F["Panell lateral:<br/>PER QUE s'ha renderitzat"]
F --> G["Diagnostic complet:<br/>component + cost + causa"]
- Els números: durada del render, temps propi i nombre de renders
En seleccionar un component en qualsevol de les dues vistes, el panell lateral mostra les seves xifres. Aquestes són les que cal saber llegir:
| Mètrica | Què mesura exactament | Com s'interpreta |
|---|---|---|
| Render duration (durada del render) | Temps del component i de tot el seu subarbre | Alta en un ancestre pot ser deguda només als seus fills: no acusis el pare sense mirar avall |
| Self time (temps propi) | Temps només d'aquest component, sense descendents | És la mètrica que assenyala el culpable real |
| Renders per commit | Quantes instàncies d'aquest component s'han renderitzat | «2.000» en una llista és el senyal clàssic |
| Durada total del commit | Tota la feina de React en aquesta actualització | El pressupost és 16 ms (un fotograma a 60 fps) |
| Ranked position | Lloc al rànquing del commit | Ataca el número 1; el número 12 rarament importa |
La distinció entre les dues primeres és la que més errors de diagnòstic evita:
PaginaCataleg render duration: 214 ms self time: 0,8 ms
LlistaBicicletes render duration: 212 ms self time: 2,1 ms
TargetaBicicleta (x2000) self time: 0,1 ms cadascuna → 209 msPaginaCataleg sembla la culpable amb 214 ms, però el seu temps propi és 0,8 ms: no fa pràcticament res. El cost està repartit entre 2.000 targetes de 0,1 ms cadascuna. El problema no és que una targeta sigui cara; és que n'hi ha 2.000. Aquest matís decideix la solució: aquí no serveix optimitzar l'interior de TargetaBicicleta, serveix evitar que s'executin (memo) o que existeixin (paginar, virtualitzar).
I el cas contrari, amb el mateix total:
Aquí sí: un sol component consumeix 178 ms per si mateix. Hi ha un càlcul pesat a dins, i l'eina és useMemo o un algorisme millor.
- Per què s'ha repintat això: les quatre causes
Amb «Record why each component rendered» activat, el panell lateral afegeix la secció «Why did this render?», i aquí hi ha el valor diferencial de l'eina. Les causes que pot indicar són exactament les quatre de 08-01:
| Missatge del Profiler | Causa | Què fer |
|---|---|---|
| «Props changed: (alReservar, alSeleccionar)» | Ha canviat la identitat d'aquestes props | Estabilitzar-les amb useCallback/useMemo (08-03), o passar primitius |
| «Hooks changed» / «State changed» | El seu propi estat ha canviat | Correcte per definició. Si sobra, l'estat està mal col·locat: baixa'l o puja'l (08-01) |
| «Context changed» | Ha canviat un context que consumeix | Dividir el context i estabilitzar el seu value (07-02 + 08-03). memo no serveix aquí |
| «The parent component rendered» | El pare s'ha renderitzat i aquest no està memoïtzat | memo (08-02), o aïllar-lo amb children (08-01) |
La primera és la més valuosa de totes, perquè anomena la prop culpable. Sense el Profiler, esbrinar quina de set props ha canviat d'identitat exigeix instrumentar el component a mà (com a 08-02). Amb ell, és una línia de text.
I la lectura estratègica de la taula, que és la que converteix el mòdul sencer en un procediment:
flowchart TD
A["Why did this render?"] --> B{"Que diu?"}
B -->|"Parent rendered"| C{"El component<br/>es car o repetit?"}
C -->|Si| D["memo 08-02"]
C -->|No| E["Deixa-ho: no es el problema"]
B -->|"Props changed"| F["Quina prop?<br/>Estabilitzar-la amb useCallback/useMemo 08-03<br/>o passar primitius"]
B -->|"State changed"| G{"Hauria de tenir<br/>aquest estat?"}
G -->|No| H["Baixar l'estat 08-01"]
G -->|Si| I["Correcte: no toquis res"]
B -->|"Context changed"| J["Dividir el context 07-02<br/>+ estabilitzar value 08-03"]
- Ressaltar actualitzacions al navegador
Abans de gravar res, hi ha un diagnòstic de cinc segons. A la pestanya Components, engranatge ⚙ → General → Highlight updates when components render.
A partir d'aquest moment, cada component que es renderitza queda envoltat d'un requadre de color durant un instant:
| Color del requadre | Freqüència de renders |
|---|---|
| Blau clar | Baixa |
| Verd | Mitjana |
| Groc | Alta |
| Vermell | Molt alta: mira aquí |
Escriu una lletra al CercadorBicicletes i observa. Si tota la pantalla s'encén —capçalera, peu, resum de flota, les 2.000 targetes—, tens el problema localitzat sense haver gravat res. Si només parpelleja el camp de text, no hi ha res a investigar per aquí.
És l'eina ideal per a tres coses: una revisió ràpida abans d'una sessió de perfilat seriosa, comprovar d'un cop d'ull si un memo acabat de posar ha fet efecte, i detectar renders continus causats per un efecte mal escrit (un component que parpelleja sense que ningú el toqui és un bucle de renders).
Desactiva-la en mesurar temps: dibuixar els requadres consumeix feina i contamina les xifres.
- Cas d'estudi: el cercador de CicloUrbano, abans i després
Aquest és el recorregut complet, amb el catàleg de 2.000 bicicletes, un build de producció amb perfilat i la CPU ralentitzada 4× per simular un mòbil de gamma mitjana.
Pas 1: definir la hipòtesi i la interacció
Símptoma reportat: «en escriure al cercador, les lletres triguen a aparèixer».
Interacció a mesurar: escriure «electr» (6 pulsacions) al CercadorBicicletes.
Pressupost (08-01): INP < 200 ms; commits < 16 ms.
Pas 2: mesurar l'estat inicial
Gravació amb la llista temporal de commits:
| Commit | Durada | Components renderitzats |
|---|---|---|
| 1 | 218 ms | 2.014 |
| 2 | 226 ms | 2.014 |
| 3 | 214 ms | 2.014 |
| 4 | 231 ms | 2.014 |
| 5 | 219 ms | 2.014 |
| 6 | 224 ms | 2.014 |
Sis commits de ~220 ms. El pressupost de 16 ms s'incompleix catorze vegades. La sensació de «les lletres triguen» queda confirmada amb números: cada tecla bloqueja el fil principal més d'un cinquè de segon.
Vista Ranked del commit 3:
1. TargetaBicicleta (x2000) 209,4 ms (self total) 2. LlistaBicicletes 2,1 ms 3. PaginaCataleg 0,8 ms 4. ResumFlota 0,7 ms 5. CercadorBicicletes 0,3 ms
Vista Flamegraph: el 95 % de l'amplada és la fila de TargetaBicicleta.
Pas 3: localitzar la causa
Se selecciona una TargetaBicicleta qualsevol i es llegeix el panell lateral:
TargetaBicicleta (bici-1487)
Render duration: 0,11 ms
Self time: 0,11 ms
Why did this render?
→ Props changed: (alSeleccionar, alReservar)Diagnòstic complet, i noteu que ja no hi ha cap suposició:
TargetaBicicletano està memoïtzada (08-02), així que es torna a executar amb cada render del pare.- Encara que ho estigués,
alSeleccionarialReservarsón funcions fletxa creades en línia aLlistaBicicletes: elmemofallaria igualment (08-03). - Cada targeta costa poc (0,11 ms), però n'hi ha 2.000.
- A més, tot el filtratge passa de forma síncrona i urgent en la mateixa actualització que el tecleig.
Pas 4: aplicar les correccions
S'apliquen, i això és important, d'una en una, mesurant entre cada pas:
4.1 — memo a TargetaBicicleta (08-02), traient a més l'Intl.NumberFormat fora del component.
const FORMAT_EURO = new Intl.NumberFormat('ca-ES', { style: 'currency', currency: 'EUR' });
function TargetaBicicleta({ bicicleta, nomEstacio, alSeleccionar, alReservar }) { /* … */ }
export default memo(TargetaBicicleta);Mesurament: sense canvis (≈220 ms). I això és un resultat, no un fracàs: confirma el punt 2 del diagnòstic. Un memo sense props estables no estalvia res.
4.2 — useCallback a LlistaBicicletes (08-03).
const gestionarSeleccio = useCallback((id) => navegar(`/bicicletas/${id}`), [navegar]);
const gestionarReserva = useCallback((id) => navegar(`/reservas/nueva?bicicleta=${id}`), [navegar]);Mesurament: els commits baixen a ~34 ms. I al panell lateral d'una targeta qualsevol, el missatge ha canviat al que es buscava:
Només es renderitzen les targetes que entren o surten del filtre. El 84 % del cost ha desaparegut amb dos hooks, però només perquè el memo del pas anterior estava posat: per separat, cap dels dos servia.
4.3 — useDeferredValue a PaginaCataleg (08-03), perquè el filtratge deixi de competir amb el tecleig.
const termeDiferit = useDeferredValue(terme);
const visibles = useMemo(
() => filtrarIOrdenar(bicicletes, estacions, termeDiferit, tipus, ordre),
[bicicletes, estacions, termeDiferit, tipus, ordre]
);Mesurament: ara hi ha dos tipus de commit per pulsació —un urgent de ~2 ms que només actualitza el camp, i un altre diferit i interrompible de ~30 ms per a la llista—, i en el tecleig ràpid diversos dels diferits s'abandonen sense arribar a completar-se. El camp respon a l'instant.
Pas 5: tornar a mesurar i comparar
| Mètrica | Abans | Després | Millora |
|---|---|---|---|
| Durada mitjana del commit | 220 ms | 30 ms (diferit) + 2 ms (urgent) | −86 % |
| Components per commit | 2.014 | 14 | −99 % |
| Commits per sobre de 16 ms | 6 de 6 | 0 urgents, 2 diferits | ✅ |
| INP mesurat | ~240 ms | ~35 ms | −85 % |
| Pressupost (< 200 ms INP) | ❌ | ✅ | Complert |
Pas 6: decidir si parar
El pressupost es compleix amb marge. Queden ~30 ms als commits diferits, i es podrien atacar virtualitzant la llista (08-01). Però: són diferits, són interrompibles, no bloquegen el tecleig i l'usuari ja no percep cap retard. Virtualitzar afegiria una dependència, complicaria l'accessibilitat i trencaria Ctrl+F, a canvi d'una millora que ningú notarà.
Decisió: parar aquí. Documentar les xifres a RENDIMENT.md i passar a una altra cosa. Aquesta decisió és tan part de l'ofici com les tres correccions anteriors.
- L'API
<Profiler> en codi
<Profiler> en codiL'extensió mesura quan tu ets davant. Per mesurar en automàtic —en proves de rendiment, en integració contínua o en producció amb usuaris reals— React ofereix un component:
import { Profiler } from 'react';
<Profiler id="cataleg" onRender={registrarMetrica}>
<PaginaCataleg />
</Profiler>La funció onRender rep sis arguments:
function registrarMetrica(
id, // 1) l'id del <Profiler>: "cataleg"
fase, // 2) "mount" | "update" | "nested-update"
duradaReal, // 3) ms d'aquest render (amb memoitzacio aplicada)
duradaBase, // 4) ms estimats SENSE cap memoitzacio
inici, // 5) marca temporal en que React va comencar
confirmacio // 6) marca temporal en que React va confirmar
) {
// …
}La parella clau és la tercera amb la quarta. duradaBase és el que costaria renderitzar tot el subarbre sense cap memo ni useMemo; duradaReal és el que ha costat de veritat. La diferència és, literalment, el que la teva memoïtzació està estalviant:
// src/utilitats/metriques.js
const LLINDAR_MS = 16;
export function registrarMetrica(id, fase, duradaReal, duradaBase) {
const estalvi = duradaBase - duradaReal;
if (duradaReal > LLINDAR_MS) {
console.warn(
`[rendiment] ${id} (${fase}): ${duradaReal.toFixed(1)} ms ` +
`(base ${duradaBase.toFixed(1)} ms, estalvi ${estalvi.toFixed(1)} ms)`
);
}
// En produccio: enviar a un servei de metriques, sense bloquejar el fil
if (import.meta.env.PROD && navigator.sendBeacon) {
navigator.sendBeacon(
'/metricas/render',
JSON.stringify({ id, fase, duradaReal, duradaBase, ts: Date.now() })
);
}
}// src/rutes.jsx (fragment)
import { Profiler } from 'react';
import { registrarMetrica } from './utilitats/metriques.js';
{
index: true,
element: (
<Profiler id="cataleg" onRender={registrarMetrica}>
<PaginaCataleg />
</Profiler>
)
}Detalls que cal conèixer abans d'usar-lo:
| Aspecte | Detall |
|---|---|
| Cost | No és gratis: afegeix feina a cada render del subarbre. Embolcalla zones concretes, no tota l'aplicació |
| En producció estàndard | onRender no es crida: cal el build amb perfilat (apartat 2) |
| Anidament | Es poden aniuar <Profiler> amb id diferents per mesurar per zones |
Un id per zona mesurada |
L'id viatja a cada crida: fes-lo servir per agregar mètriques per pantalla |
| No mesura el DOM ni la xarxa | Només la feina de React. Per a la resta, apartat 10 |
nested-update |
Un render provocat per un setEstat dins d'un efecte de disposició: senyal d'un patró millorable |
Un ús molt pràctic en proves automatitzades: registrar duradaReal en un fitxer durant una prova d'extrem a extrem i fer fallar la construcció si supera un llindar. És la manera que un pressupost de rendiment es defensi sol, sense dependre que algú es recordi de mesurar. Aquest tipus de comprovació automàtica és, precisament, el terreny del Mòdul 9.
- Complements fora de React
El Profiler mesura la feina de React, i la feina de React és una part del total. Aquestes són les eines que cobreixen la resta, esmentades perquè sàpigues quan canviar d'instrument:
| Eina | Què mesura que el Profiler no | Quan usar-la |
|---|---|---|
| Pestanya Rendiment del navegador | Tot el fil principal: JavaScript, maquetació, pintat, xarxa, tasques llargues | Quan el Profiler diu que React triga poc i tot i així se sent lent |
| Lighthouse | LCP, CLS, TBT, bones pràctiques, accessibilitat, amb una nota global | Auditoria periòdica i abans d'un desplegament important |
| Pestanya Xarxa | Mida i ordre dels fragments, cascades de peticions | En validar la divisió de codi de 08-04 |
web-vitals (biblioteca) |
LCP, INP i CLS d'usuaris reals en producció | El mesurament que de veritat importa: el dels teus usuaris, no el del teu portàtil |
| Ralentització de CPU i xarxa | Simulació de dispositius i connexions lentes | Sempre que mesuris: 4× de CPU i «3G lent» |
PerformanceObserver |
Tasques llargues, entrades d'usuari, marques pròpies | Instrumentació a mida en producció |
La regla per triar: si el Profiler diu que React consumeix poc temps i l'aplicació continua sentint-se lenta, el problema no és de React. Serà a la xarxa, a les imatges, al CSS, a la maquetació o en una biblioteca de tercers, i la pestanya Rendiment del navegador és on es veu.
Sobre web-vitals convé insistir en una cosa: el teu portàtil amb localhost no és una mostra representativa de res. Mesurar en producció amb usuaris reals revela distribucions —el percentil 75 de l'INP en mòbils Android, per exemple— que cap mesurament local mostra. No es desenvolupa en aquesta lliçó, però és el destí natural de tot el que s'ha après aquí.
- Quan parar d'optimitzar
És la pregunta que tanca el mòdul, i la que menys s'ensenya. Hi ha quatre senyals clares d'aturada:
1. El pressupost es compleix. Si vas definir INP < 200 ms i estàs en 35 ms, has acabat. Un pressupost que no atura la feina quan es compleix no era un pressupost.
2. La millora ja no es percep. Existeix un llindar perceptiu: per sota d'uns ~100 ms una resposta se sent instantània. Passar de 40 ms a 25 ms és un 37 % de millora al full de càlcul i zero millora per a l'usuari.
3. El cost de lectura supera el benefici. Un component amb quatre useMemo, tres useCallback i un comparador personalitzat, per estalviar 3 ms, és un component que el proper desenvolupador —o tu mateix d'aquí a sis mesos— trencarà en modificar-lo. Aquest risc té un cost real.
4. Hi ha un problema més gran en un altre lloc. Si l'aplicació triga 3 s en el primer pintat, continuar polint un commit de 20 ms és dedicar esforç al lloc equivocat. Torna al pas 1 del flux de 08-01 i tria el coll d'ampolla més gran, no el més entretingut.
I el senyal que cal tornar enrere: si una optimització no ha produït una millora mesurable, reverteix-la. El seu cost de complexitat i memòria continua allà encara que el benefici no existeixi. Aquesta disciplina és el que impedeix que un projecte s'ompli de memoïtzació decorativa al llarg dels anys.
flowchart TD
A["He aplicat una optimitzacio"] --> B["Tornar a mesurar<br/>build de produccio, CPU 4x"]
B --> C{"Millora<br/>mesurable?"}
C -->|No| D["REVERTIR<br/>cost sense benefici"]
C -->|Si| E{"Es compleix<br/>el pressupost?"}
E -->|No| F["Seguent coll d'ampolla<br/>el MES GRAN, no el mes comode"]
E -->|Si| G{"Algu percebria<br/>seguir millorant?"}
G -->|Si| F
G -->|No| H["PARAR<br/>documentar les xifres i seguir"]
F --> A
D --> F
Errors Comuns i Consells
Mesurar en mode desenvolupament i creure's les xifres. StrictMode duplica els renders i els avisos afegeixen cost. Localitza en desenvolupament; quantifica en un build de producció amb perfilat.
Gravar massa. Una gravació amb cinc interaccions diferents és il·legible. Una gravació, una hipòtesi.
Perfilar sense «Record why each component rendered». És renunciar a la meitat de l'eina: sense la causa, tens temps però no diagnòstic.
Confondre «render duration» amb «self time». El primer inclou els descendents. Acusar un pare amb 214 ms de durada i 0,8 ms de temps propi és començar a optimitzar el lloc equivocat.
Perseguir el nombre de components renderitzats. 2.000 renders de 0,01 ms importen menys que un de 180 ms. La mètrica és el temps.
Oblidar-se de keep_fnames en construir per perfilar. El gràfic de flames ple de t, e i n no serveix per a res.
Deixar <Profiler> embolcallant tota l'aplicació en producció. Afegeix cost a cada render. Embolcalla zones concretes i només mentre ho necessitis.
Consell: guarda les gravacions. El Profiler permet exportar un perfil a JSON i importar-lo després. Guardar l'«abans» junt amb el «després» converteix una millora en una prova, i és material excel·lent per a una revisió de codi.
Consell: ralentitza sempre la CPU 4×. És la diferència entre optimitzar per al teu portàtil i optimitzar per als teus usuaris.
Consell: escriu les xifres al repositori. Un RENDIMENT.md amb la interacció mesurada, la data, l'abans i el després converteix la feina d'aquest mòdul en alguna cosa que sobreviu a qui la va fer.
Consell: perfila també quan tot va bé. Un mesurament periòdic detecta regressions quan encara són petites. Trobar que un commit ha passat de 12 ms a 40 ms en l'última setmana és molt més fàcil que investigar per què l'aplicació «va lenta» sis mesos després.
Exercicis
Exercici 1. Interpreta aquesta gravació de PaginaDetallEstacio de CicloUrbano, presa en un build de producció en prémer la pestanya «Incidències». Digues quin és el problema, què no és el problema, i quina tècnica de quina lliçó aplicaries.
Commit 1 — 187 ms — 63 components Ranked: 1. PestanyaIncidencies 171,2 ms (self: 168,9 ms) 2. TargetaEstacio 6,4 ms (self: 0,4 ms) 3. MollesDePa 3,1 ms (self: 3,1 ms) 4. Capcalera 2,8 ms (self: 0,2 ms) Panell de PestanyaIncidencies: Why did this render? → The parent component rendered
Exercici 2. Després d'aplicar memo a TargetaEstacio a PaginaEstacions, la nova gravació mostra això. Explica per què el memo no ha servit, quina informació concreta t'ho diu i com ho arreglaries.
Commit 1 — 78 ms — 34 components Panell de TargetaEstacio (est-02): Render duration: 2,1 ms Self time: 1,9 ms Why did this render? → Props changed: (placesLliures, alAbrir, estil)
Exercici 3. Escriu un embolcall <Profiler> que registri a sessionStorage el pitjor commit de cada zona mesurada (id), guardant durada, fase i moment, i que avisi per consola quan una zona superi els 16 ms dues vegades seguides. Explica on la col·locaries a CicloUrbano i per què no a l'arrel.
Solucions
Solució 1. El problema: PestanyaIncidencies consumeix 168,9 ms de temps propi. No és un problema de quantitat de components —només n'hi ha 63 en tot el commit— sinó d'un sol component que fa una feina pesada dins del seu render: probablement ordena, agrupa o formata les incidències, i molt possiblement amb un find dins d'un map o amb Intl creat per element.
El que NO és el problema:
- No és
TargetaEstacio: 6,4 ms de durada però 0,4 ms propis. El seu cost és el dels seus fills. - No és el nombre de renders: 63 components és una xifra perfectament sana.
- No és «The parent component rendered», encara que sigui la causa indicada. Que el pare renderitzi és normal en canviar de pestanya; embolcallar
PestanyaIncidenciesenmemono estalviaria res, perquè aquest render havia de passar: l'usuari acaba de prémer la pestanya.
Tècnica a aplicar: useMemo sobre el càlcul pesat (08-03), mesurant abans amb console.time per confirmar que és car. I abans que això, revisar l'algorisme: un índex Map en lloc de cerques lineals i un Intl.Collator reutilitzat solen baixar més el cost que la memoïtzació. Si després d'això el primer render continua pesant, l'opció següent és que el càlcul no passi al client (select de useQuery, o directament el servidor).
Solució 2. Per què no ha servit: la línia Props changed: (placesLliures, alAbrir, estil) ho diu literalment. Tres props canvien d'identitat a cada render del pare, així que la comparació superficial de memo falla sempre i el component s'executa igual, amb la comparació com a cost afegit.
Quina informació ho diu: la secció «Why did this render?» amb la llista de props. Sense ella caldria instrumentar el component a mà amb un comparador de rastreig (08-02).
Com arreglar-ho, prop a prop:
alAbrir: funció fletxa en línia. S'estabilitza ambuseCallbackal pare, amb[navegar]o[despatxar]com a dependència (08-03).estil: objecte literal, gairebé segur unstyle={{ … }}. El millor no és memoïtzar-lo, sinó eliminar-lo: passar-lo a una classe de CSS Modules, que és la convenció del projecte.placesLliures: si és un número, la comparació per valor hauria d'encertar. Que aparegui com a canviada significa que realment canvia —potser es recalcula amb unfilterque retorna un valor diferent— o que no és un primitiu. Caldria inspeccionar el valor a la pestanya Components. Si la dada canvia de veritat, aquest render és correcte i no hi ha res a arreglar.
I la comprovació final, que és el que tanca el cicle: tornar a gravar. El panell ha de passar a dir Did not render a les targetes les dades de les quals no han canviat. Si no ho diu, l'arranjament no ha funcionat i cal tornar al diagnòstic.
Solució 3.
// src/utilitats/metriques.js
const LLINDAR_MS = 16;
const CLAU = 'metriques:pitjors';
const consecutius = new Map(); // id → nombre de commits seguits per sobre del llindar
function llegirPitjors() {
try {
return JSON.parse(sessionStorage.getItem(CLAU) ?? '{}');
} catch {
return {}; // sessionStorage pot fallar en mode privat
}
}
export function registrarMetrica(id, fase, duradaReal, duradaBase, inici) {
// 1) Guardar el pitjor commit per zona
const pitjors = llegirPitjors();
const pitjorActual = pitjors[id]?.duradaReal ?? 0;
if (duradaReal > pitjorActual) {
pitjors[id] = {
duradaReal: Number(duradaReal.toFixed(2)),
duradaBase: Number(duradaBase.toFixed(2)),
estalvi: Number((duradaBase - duradaReal).toFixed(2)),
fase,
moment: new Date(performance.timeOrigin + inici).toISOString()
};
try {
sessionStorage.setItem(CLAU, JSON.stringify(pitjors));
} catch { /* quota plena o mode privat: no és motiu per trencar l'app */ }
}
// 2) Avisar només despres de DOS commits seguits per sobre del llindar
if (duradaReal > LLINDAR_MS) {
const seguits = (consecutius.get(id) ?? 0) + 1;
consecutius.set(id, seguits);
if (seguits >= 2) {
console.warn(
`[rendiment] "${id}" porta ${seguits} commits seguits per sobre de ` +
`${LLINDAR_MS} ms (ultim: ${duradaReal.toFixed(1)} ms, ` +
`base ${duradaBase.toFixed(1)} ms). Perfila aquesta zona.`
);
}
} else {
consecutius.set(id, 0); // un commit rapid reinicia la ratxa
}
}
export function obtenirPitjors() {
return llegirPitjors(); // per bolcar-ho al final d'una prova E2E
}// src/rutes.jsx (fragment)
{
index: true,
element: (
<Profiler id="cataleg" onRender={registrarMetrica}>
<PaginaCataleg />
</Profiler>
)
},
{
path: 'estaciones/:estacionId',
element: (
<Profiler id="detall-estacio" onRender={registrarMetrica}>
<Mandrosa><PaginaDetallEstacio /></Mandrosa>
</Profiler>
)
}On col·locar-la i per què no a l'arrel:
- S'embolcalla per zones amb significat propi —el catàleg, el detall d'estació, el panell de taller—, perquè l'
idés el que permet atribuir un problema a una pantalla concreta. Un únic<Profiler id="app">donaria un número global que no assenyala res. - No a l'arrel, per tres raons:
onRenderes dispara a cada render de tot el subarbre, i a l'arrel això és tots els renders de l'aplicació (cost mesurable afegit); la durada agregada barreja feina de zones independents i perd tota capacitat de diagnòstic; i el llindar de 16 ms deixa de tenir sentit quan el que es mesura és «tota l'aplicació» en lloc d'una interacció concreta. - L'avís després de dos commits seguits evita el soroll dels pics aïllats —una càrrega inicial, una revalidació de Query— i assenyala només el que és sostingut, que és el que l'usuari percep.
Conclusió
El React DevTools Profiler és l'eina que converteix tot aquest mòdul en un procediment verificable. El seu valor està a respondre tres preguntes amb dades: què s'ha renderitzat, quant ha costat i per què. Sense la tercera, les altres dues només alimenten sospites.
El mètode complet, tal com l'has aplicat: mesurar en un build de producció (npm run build + npm run preview, amb l'àlies a react-dom/profiling i keep_fnames perquè els noms continuïn sent llegibles), perquè el mode de desenvolupament duplica els renders amb StrictMode, afegeix avisos, serveix mòduls sense empaquetar i arrossega mapes de fonts; localitzar en desenvolupament, quantificar en producció. Gravar una sola interacció per sessió, amb «Record why each component rendered» sempre activat. Llegir la línia temporal de commits —cada barra és una aplicació de canvis al DOM, amb el pressupost de 16 ms per fotograma—, usar el gràfic ordenat per saber què costa i el gràfic de flames per saber on és a l'arbre. I no confondre mai «render duration» (el component i tot el seu subarbre) amb «self time» (només ell): un pare de 214 ms amb 0,8 ms propis no és el culpable, i aquesta distinció decideix si la solució és memoïtzar, virtualitzar o canviar un algorisme.
La secció «Why did this render?» es correspon exactament amb les quatre causes de 08-01, i cadascuna té la seva resposta: props changed amb el nom de la prop culpable → estabilitzar-la o passar primitius (08-03); state changed → correcte, tret que l'estat estigui mal col·locat (08-01); context changed → dividir el context i estabilitzar el seu value (07-02 + 08-03), on memo no serveix de res; the parent component rendered → memo si el component és car o repetit (08-02), o aïllar-lo amb children. A això s'hi suma «Highlight updates», el diagnòstic de cinc segons que encén en vermell el que es repinta massa.
El cas d'estudi de CicloUrbano ha recorregut el cicle sencer amb xifres reals: sis commits de ~220 ms amb 2.014 components en escriure sis lletres; el diagnòstic exacte —Props changed: (alSeleccionar, alReservar)—; i les tres correccions aplicades d'una en una, mesurant entre cada pas. El memo sol no va canviar res, i això va ser un resultat valuós, no un fracàs: va confirmar que la memoïtzació sense identitats estables és cost pur. Amb useCallback els commits van baixar a 34 ms i les targetes van passar a dir Did not render; amb useDeferredValue el tecleig va deixar de competir amb el filtratge. De 220 ms a 30 ms, de 2.014 components a 14, un INP de 240 ms a 35 ms. I l'última decisió, tan important com les anteriors: parar, encara que quedés marge tècnic, perquè virtualitzar hauria afegit complexitat i trencat l'accessibilitat a canvi d'una millora imperceptible. També coneixes l'API <Profiler> amb el seu onRender i la parella duradaReal/duradaBase, que mesura directament el que la teva memoïtzació està estalviant, i saps que el Profiler no ho veu tot: per a maquetació, pintat, xarxa i usuaris reals hi ha la pestanya Rendiment, Lighthouse i web-vitals.
Amb això es tanca el Mòdul 8. CicloUrbano ja no repinta 2.000 targetes per cada tecla, no recalcula el que no ha canviat, no crea funcions noves on algú compara identitats, no descarrega el panell de taller per ensenyar el catàleg i —el que més val— no depèn que ningú endevini res: cada decisió d'aquest mòdul es recolza en un número reproduïble, i la disciplina de revertir el que no millora impedeix que el projecte s'ompli d'optimització decorativa. Però fixa't en el que ha calgut per arribar aquí: s'ha canviat la signatura de LlistaBicicletes, s'ha mogut estat de lloc, s'ha reescrit el value de dos proveïdors de context, s'han partit les rutes en nou fragments i s'ha afegit una capa de càrrega mandrosa amb els seus fallats de xarxa. Nou refactoritzacions sobre codi que funcionava, i l'única comprovació ha estat obrir el navegador i mirar. Això no escala: la propera vegada que algú estabilitzi una dependència i se li escoli un valor congelat, o que un memo deixi d'actualitzar un camp, ningú se n'assabentarà fins que ho expliqui un usuari. El Mòdul 9: Proves en React ataca exactament aquest buit: per què una aplicació sense proves no es pot refactoritzar amb confiança, què val la pena provar i què no, i com escriure comprovacions que fallin quan el comportament canviï —no quan canviï la implementació—. La propera lliçó és Introducció a les Proves.
Curs de React
Mòdul 1: Introducció a React
- Què és React?
- Configuració de l'Entorn de Desenvolupament
- Hola Món amb React
- JSX: Extensió de Sintaxi de JavaScript
- Com Renderitza React: Virtual DOM i Reconciliació
Mòdul 2: Components de React
- Entendre els Components
- Components Funcionals vs de Classe
- Props: Passar Dades als Components
- State: Gestió de l'Estat del Component
- Estils en els Components: CSS, Mòduls i Utilitats
Mòdul 3: Treballar amb Esdeveniments
- Gestió d'Esdeveniments a React
- Renderitzat Condicional
- Llistes i Claus
- Formularis i Components Controlats
- Validació de Formularis i Components No Controlats
- Accessibilitat en Components Interactius
Mòdul 4: Conceptes Avançats de Components
- Elevar l'Estat
- Composició vs Herència
- Mètodes del Cicle de Vida de React
- Hooks: Introducció i Ús Bàsic
- Límits d'Error: Capturar Fallades a la Interfície
Mòdul 5: Hooks de React
- Hook useState
- Hook useEffect
- Hook useRef i Accés al DOM
- Hook useContext
- Hook useReducer
- Hooks Personalitzats
Mòdul 6: Enrutament a React
- Introducció a React Router
- Configuració de React Router
- Rutes Imbricades
- Navegació Programàtica
- Rutes Protegides i Control d'Accés
Mòdul 7: Gestió de l'Estat
- Introducció a la Gestió de l'Estat
- API de Context
- Redux: Introducció i Configuració
- Redux: Accions i Reductors
- Redux: Connectar-lo a React
- Estat del Servidor: Peticions, Memòria Cau i Sincronització
Mòdul 8: Optimització del Rendiment
- Tècniques d'Optimització del Rendiment a React
- Memoïtzació amb React.memo
- Hooks useMemo i useCallback
- Divisió de Codi i Càrrega Mandrosa
- Mesurar el Rendiment amb React DevTools Profiler
Mòdul 9: Proves a React
- Introducció a les Proves
- Proves Unitàries amb Jest
- Proves de Components amb React Testing Library
- Proves de Codi Asíncron i Simulació d'APIs
- Proves d'Extrem a Extrem amb Cypress
Mòdul 10: Temes Avançats
- Renderitzat al Servidor (SSR) amb Next.js
- Generació de Llocs Estàtics (SSG) amb Next.js
- Suspense i React Server Components
- TypeScript amb React
- React Native: Creació d'Aplicacions Mòbils
