Un component no existeix sempre. Apareix en pantalla, s'actualitza mentre canvien les seves props o el seu estat, i desapareix quan ja no fa falta. Aquestes tres fases —muntatge, actualització i desmuntatge— existeixen a React des del primer dia, i durant anys es van gestionar amb un conjunt de mètodes especials que només tenen els components de classe. Avui no escriuràs cap d'aquests mètodes en codi nou, però els trobaràs sense falta a qualsevol projecte amb uns quants anys a sobre, i els tutorials antics segueixen explicant React en aquests termes. En aquesta lliçó aprendràs a llegir el cicle de vida clàssic, a entendre quin problema resolia cada mètode, a traduir-lo a la sintaxi moderna amb una taula d'equivalències, i —el més important— per què el model mental correcte a React 19 ja no és «moments del cicle de vida» sinó sincronització amb un sistema extern.
Contingut
- Les tres fases de la vida d'un component
- Els mètodes de muntatge:
constructorirender componentDidMount: quan el component ja és en pantallacomponentDidUpdate: la comparació amb props i estat previscomponentWillUnmounti les fuites de memòriashouldComponentUpdatei els mètodesUNSAFE_- Exemple complet:
PanellActivitatamb temporitzador - Taula d'equivalències: classe → funció amb hooks
- Per què l'equivalència és només aproximada
- Conviure amb classes en una base de codi real
- Les tres fases de la vida d'un component
Tot component muntat al DOM travessa aquest recorregut:
flowchart TD
A["MUNTATGE<br/>El component entra a l'arbre"] --> A1["constructor()"]
A1 --> A2["render()"]
A2 --> A3["React insereix el DOM"]
A3 --> A4["componentDidMount()"]
A4 --> B{"Canvien props<br/>o estat?"}
B -- "sí" --> B1["shouldComponentUpdate()<br/>(opcional)"]
B1 --> B2["render()"]
B2 --> B3["React actualitza el DOM"]
B3 --> B4["componentDidUpdate()"]
B4 --> B
B -- "el component surt de l'arbre" --> C["componentWillUnmount()"]
C --> D["DESMUNTATGE<br/>React retira el DOM i oblida l'estat"]
Tres idees abans d'entrar en el detall:
- El muntatge passa una sola vegada per instància. Si el component es desmunta i es torna a muntar, és una instància nova: estat a zero i
componentDidMountuna altra vegada. És exactament el que vas veure a 01-05, quan un canvi de tipus d'element destrueix el subarbre i perd el seu estat. - L'actualització passa moltes vegades, una per cada canvi de props o d'estat.
- El desmuntatge passa una sola vegada, i és l'última oportunitat de netejar el que el component hagi deixat obert fora de React.
I un avís sobre StrictMode, que ja coneixes de 01-03: en desenvolupament React munta, desmunta i torna a muntar cada component a propòsit. Veuràs componentDidMount dues vegades i componentWillUnmount una entre mig. No és cap error: és una comprovació que la teva neteja funciona. En producció passa una sola vegada.
- Els mètodes de muntatge:
constructor i render
constructor i renderimport { Component } from 'react';
class ComptadorLloguers extends Component {
constructor(props) {
super(props); // OBLIGATORI i sempre el primer
this.state = { lloguers: 0 }; // única assignació directa permesa
this.gestionarLloguer = this.gestionarLloguer.bind(this); // el bind de 02-02
}
gestionarLloguer() {
this.setState((estatPrevi) => ({ lloguers: estatPrevi.lloguers + 1 }));
}
render() {
return (
<div>
<p>Lloguers registrats: {this.state.lloguers}</p>
<button type="button" onClick={this.gestionarLloguer}>Registrar lloguer</button>
</div>
);
}
}constructor(props)s'executa una vegada, abans del primer render. Serveix per a dues coses i només dues: inicialitzarthis.statei enllaçar mètodes ambbind.super(props)és obligatori; sense ell,this.propsestaria buit dins del constructor i JavaScript llançaria un error en usarthis.render()ha de ser pur: mirathis.propsithis.state, retorna JSX i no fa res més. Cap petició al servidor, capsetState, cap tocar el DOM directament. React pot cridar-lo diverses vegades abans de reflectir res en pantalla, així que qualsevol efecte secundari dins derenders'executaria un nombre imprevisible de vegades.
Aquesta exigència de puresa és la raó de ser de tot el que ve després: si render no pot tenir efectes secundaris, calen altres llocs on posar-los. Els mètodes del cicle de vida són aquests llocs.
componentDidMount: quan el component ja és en pantalla
componentDidMount: quan el component ja és en pantallaS'executa una sola vegada, just després que React hagi inserit el component al DOM real. És el lloc clàssic per a tot el que necessita que l'element existeixi.
componentDidMount() {
// 1. Demanar dades al servidor
fetch('/api/bicicletas')
.then((resposta) => resposta.json())
.then((bicicletes) => this.setState({ bicicletes, carregant: false }));
// 2. Subscriure's a alguna cosa externa
window.addEventListener('resize', this.gestionarRedimensio);
// 3. Arrencar temporitzadors
this.temporitzador = setInterval(this.actualitzarRellotge, 1000);
// 4. Mesurar el DOM, que ja existeix
const alt = this.contenidor.current.offsetHeight;
}Aquí sí que està permès cridar setState: provoca un segon render immediat, abans que el navegador pinti, així que la persona usuària no veu cap parpelleig. És el patró habitual de «carregant → dades llestes».
El que no s'ha de fer: cridar setState incondicionalment amb un valor que es pugui calcular durant el render. Això duplica la feina sense motiu.
componentDidUpdate: la comparació amb props i estat previs
componentDidUpdate: la comparació amb props i estat previsS'executa després de cada actualització, excepte la primera. La seva signatura és la part que cal entendre:
componentDidUpdate(propsPrevies, estatPrevi) {
// this.props i this.state ja tenen els valors NOUS
// propsPrevies i estatPrevi tenen els ANTERIORS
}React lliura els valors anteriors perquè, sense ells, seria impossible saber què ha canviat. I aquesta comparació no és opcional: és obligatòria si a dins hi vas a cridar setState.
// CORRECTE: la comparació evita el bucle infinit
componentDidUpdate(propsPrevies) {
if (propsPrevies.estacioId !== this.props.estacioId) {
this.carregarBicicletesDeEstacio(this.props.estacioId);
}
}// TRENCAT: bucle infinit garantit
componentDidUpdate() {
this.carregarBicicletesDeEstacio(this.props.estacioId);
// càrrega -> setState -> actualització -> componentDidUpdate -> càrrega -> …
}El cicle és exactament aquest: setState provoca una actualització, l'actualització provoca componentDidUpdate, i componentDidUpdate torna a cridar setState. El navegador es queda bloquejat i React acaba llançant «Maximum update depth exceeded».
Aquest mètode és també l'origen d'una duplicació clàssica en codi antic: la càrrega inicial va a componentDidMount i la recàrrega en canviar d'estació va a componentDidUpdate, amb la mateixa crida escrita dues vegades en dos mètodes separats per cinquanta línies. N'hi ha prou de tocar-ne una i oblidar l'altra perquè aparegui un error desconcertant. Aquest defecte estructural és una de les raons per les quals van néixer els hooks.
componentWillUnmount i les fuites de memòria
componentWillUnmount i les fuites de memòriaS'executa just abans que React retiri el component del DOM. És l'última oportunitat de desfer el que es va fer en muntar.
componentWillUnmount() {
clearInterval(this.temporitzador);
window.removeEventListener('resize', this.gestionarRedimensio);
this.subscripcio.cancelar();
}Què passa si s'oblida, amb el cas més il·lustratiu, un setInterval:
- El component es munta i arrenca un interval que s'executa cada segon.
- La persona usuària navega a una altra pantalla i el component es desmunta.
- L'interval segueix viu:
setIntervalés del navegador, no de React, i a React mai se li va informar que calia parar-lo. - Cada segon, el callback intenta fer
setStatesobre un component que ja no existeix. En el millor cas React ho ignora; en el pitjor, el callback manté vives per referència totes les variables del component —inclosos les seves dades— i el navegador no les pot alliberar. - Si la persona entra i surt d'aquesta pantalla vint vegades, hi ha vint intervals executant-se alhora. L'aplicació s'alenteix sense causa aparent.
Això és una fuita de memòria, i és l'error més comú associat al cicle de vida. La regla és simètrica i sense excepcions:
| Si en muntar fas… | En desmuntar has de… |
|---|---|
setInterval / setTimeout |
clearInterval / clearTimeout |
addEventListener |
removeEventListener amb la mateixa referència de funció |
Obrir un WebSocket o un EventSource |
Tancar-lo |
| Subscriure't a un servei | Cancel·lar la subscripció |
| Llançar una petició la resposta de la qual actualitza estat | Avortar-la o marcar el component com a desmuntat |
El matís de removeEventListener mereix un avís: només elimina el gestor si li passes exactament la mateixa funció que vas registrar. Amb window.addEventListener('resize', () => this.algo()) i window.removeEventListener('resize', () => this.algo()) estàs registrant una funció i eliminant-ne una altra de diferent, encara que el codi sigui idèntic. Per això els gestors es guarden a this o s'enllacen al constructor.
shouldComponentUpdate i els mètodes UNSAFE_
shouldComponentUpdate i els mètodes UNSAFE_shouldComponentUpdate(propsSeguents, estatSeguent) retorna un booleà: true (per defecte) per renderitzar, false per saltar-se el render.
shouldComponentUpdate(propsSeguents) {
// Només re-renderitza si canvia la bicicleta o el seu estat
return (
propsSeguents.bicicleta.id !== this.props.bicicleta.id ||
propsSeguents.bicicleta.estat !== this.props.bicicleta.estat
);
}És una eina de rendiment, no de correcció, i perillosa si es fa servir malament: una comparació incompleta fa que el component ignori canvis reals i mostri dades desactualitzades, amb un error molt difícil de diagnosticar. La regla és la mateixa que s'aplicarà al Mòdul 8: no l'usis fins haver mesurat un problema real.
Va existir també PureComponent, una classe base que implementa shouldComponentUpdate fent una comparació superficial de totes les props i l'estat. El seu equivalent modern és React.memo (08-02).
Els mètodes obsolets amb prefix UNSAFE_
Tres mètodes antics van ser marcats com a insegurs a React 16.3 i renombrats amb el prefix UNSAFE_:
| Mètode | Què feia | Per què es desaconsella |
|---|---|---|
UNSAFE_componentWillMount |
S'executava abans del primer render | No hi ha DOM encara; s'usava malament per demanar dades i no funciona amb renderitzat en servidor |
UNSAFE_componentWillReceiveProps |
Avisava de props noves abans de renderitzar | Convidava a copiar props a l'estat, l'error de 04-01 |
UNSAFE_componentWillUpdate |
S'executava abans d'aplicar els canvis | No pot llegir el DOM ni cridar setState amb seguretat |
El motiu comú: no són segurs amb el renderitzat interrompible. Des de React 18, React pot començar a renderitzar, pausar i descartar la feina. Un mètode que s'executa abans del render pot arribar a executar-se diverses vegades o per a un render que s'abandona. Els mètodes que s'executen després (componentDidMount, componentDidUpdate) no tenen aquest problema, perquè per llavors el resultat ja està confirmat.
Si els veus en codi real: encara es poden usar, amb el seu prefix, però són un senyal clar de codi sense migrar.
- Exemple complet:
PanellActivitat amb temporitzador
PanellActivitat amb temporitzadorAquest és un component de CicloUrbano escrit com a classe, tal com podria estar en una versió antiga del projecte. Mostra l'hora actual i quant de temps porta activa la reserva res-01.
// src/components/PanellActivitat.jsx — CODI HERETAT (classe)
import { Component } from 'react';
import estils from './PanellActivitat.module.css';
/**
* Panell d'activitat d'una reserva en curs de CicloUrbano.
* Props:
* - reserva (objecte Reserva, obligatori)
*/
class PanellActivitat extends Component {
constructor(props) {
super(props);
this.state = {
ara: new Date(),
registres: 0
};
this.actualitzarRellotge = this.actualitzarRellotge.bind(this);
}
// MUNTATGE: el component ja és en pantalla, arrenquem el temporitzador
componentDidMount() {
console.log('[PanellActivitat] muntat, arrencant temporitzador');
this.temporitzador = setInterval(this.actualitzarRellotge, 1000);
}
// ACTUALITZACIÓ: només reaccionem si canvia la reserva mostrada
componentDidUpdate(propsPrevies) {
if (propsPrevies.reserva.id !== this.props.reserva.id) {
console.log('[PanellActivitat] la reserva ha canviat, reiniciant comptador');
this.setState({ registres: 0 });
}
}
// DESMUNTATGE: donem de baixa el temporitzador. SENSE AIXÒ HI HA FUITA
componentWillUnmount() {
console.log('[PanellActivitat] desmuntat, aturant temporitzador');
clearInterval(this.temporitzador);
}
actualitzarRellotge() {
this.setState((estatPrevi) => ({
ara: new Date(),
registres: estatPrevi.registres + 1
}));
}
render() {
const { reserva } = this.props;
const inici = new Date(reserva.dataInici);
const minutsTranscorreguts = Math.max(
0,
Math.floor((this.state.ara - inici) / 60000)
);
const minutsContractats = reserva.hores * 60;
const restants = minutsContractats - minutsTranscorreguts;
return (
<section className={estils.panell}>
<h2>Activitat de la reserva {reserva.id}</h2>
<p>Hora actual: {this.state.ara.toLocaleTimeString('ca-ES')}</p>
<p>Minuts transcorreguts: {minutsTranscorreguts}</p>
<p className={restants < 15 ? estils.urgent : undefined}>
{restants > 0
? `Queden ${restants} minuts de reserva.`
: 'La reserva ha finalitzat.'}
</p>
</section>
);
}
}
export default PanellActivitat;Recorregut per les peces:
constructor: inicialitza dues dades d'estat i fa elbinddeactualitzarRellotge, perquè es passarà asetIntervali allà perdria el seuthis(02-02).componentDidMount: arrenca l'interval i guarda el seu identificador athis.temporitzador. Es guarda a la instància, no a l'estat, perquè no afecta el que es pinta i canviar-lo no ha de provocar cap render. És el mateix raonament que justificaràuseRefa 05-03.componentDidUpdate: la comparaciópropsPrevies.reserva.id !== this.props.reserva.idés imprescindible. Sense ella, elsetStates'executaria a cada actualització —i n'hi ha una per segon— provocant el bucle de l'apartat 4.componentWillUnmount: elclearIntervalque evita la fuita. Prova-ho de comentar, munta i desmunta el panell diverses vegades i mira la consola: els missatges del rellotge s'acumulen, un per cada instància morta.render: pur.minutsTranscorreguts,restantsi la classe condicional són valors derivats calculats a cada render, no estat. La mateixa distinció de 02-04, ara en sintaxi de classe.
Aquest mateix component, escrit amb hooks, ocupa la meitat:
// src/components/PanellActivitat.jsx — VERSIÓ MODERNA (avanç de 05-02)
import { useState, useEffect } from 'react';
import estils from './PanellActivitat.module.css';
function PanellActivitat({ reserva }) {
const [ara, setAra] = useState(new Date());
useEffect(() => {
const temporitzador = setInterval(() => setAra(new Date()), 1000);
return () => clearInterval(temporitzador); // la neteja, AL COSTAT del que neteja
}, []);
const inici = new Date(reserva.dataInici);
const minutsTranscorreguts = Math.max(0, Math.floor((ara - inici) / 60000));
const restants = reserva.hores * 60 - minutsTranscorreguts;
return (
<section className={estils.panell}>
<h2>Activitat de la reserva {reserva.id}</h2>
<p>Hora actual: {ara.toLocaleTimeString('ca-ES')}</p>
<p>Minuts transcorreguts: {minutsTranscorreguts}</p>
<p className={restants < 15 ? estils.urgent : undefined}>
{restants > 0 ? `Queden ${restants} minuts de reserva.` : 'La reserva ha finalitzat.'}
</p>
</section>
);
}
export default PanellActivitat;Observa la diferència que més importa: el clearInterval és tres línies més avall del setInterval, dins del mateix bloc, en lloc d'a cinquanta línies de distància en un altre mètode. Oblidar-lo es torna molt més difícil. L'API completa de useEffect —què és aquesta funció que es retorna, què significa l'array buit del final— s'explica a 05-02; aquí només interessa la comparació estructural.
- Taula d'equivalències: classe → funció amb hooks
| Mètode de classe | Equivalent amb hooks | Lliçó |
|---|---|---|
constructor + this.state = {…} |
useState(valorInicial) |
05-01 |
this.setState({…}) |
La funció actualitzadora de useState |
05-01 |
render() |
El cos de la funció, amb el seu return |
02-02 |
componentDidMount |
useEffect(() => { … }, []) |
05-02 |
componentDidUpdate |
useEffect(() => { … }, [dependencies]) |
05-02 |
componentWillUnmount |
La funció de neteja que retorna useEffect |
05-02 |
shouldComponentUpdate |
React.memo al voltant del component |
08-02 |
PureComponent |
React.memo |
08-02 |
this.temporitzador = … (dada no visual) |
useRef |
05-03 |
static contextType |
useContext |
05-04 |
this.elMeuNode = createRef() |
useRef |
05-03 |
| HOC o render props (04-02) | Hook personalitzat | 05-06 |
getDerivedStateFromError / componentDidCatch |
Sense equivalent: segueixen exigint classe | 04-05 |
UNSAFE_componentWillReceiveProps |
Gairebé sempre: res. Recalcula durant el render | 04-01 |
L'última fila mereix èmfasi. UNSAFE_componentWillReceiveProps s'usava sobretot per copiar props a l'estat quan canviaven, i aquesta necessitat gairebé mai és real: si un valor depèn de les props, es calcula durant el render com a valor derivat, tal com vas fer amb bicicletesVisibles a 04-01. L'absència d'equivalent no és una mancança dels hooks: és que el problema estava mal plantejat.
- Per què l'equivalència és només aproximada
La taula anterior és una ajuda per traduir, no una descripció de com funciona React modern. Si et quedes amb la idea que «useEffect amb array buit és componentDidMount», escriuràs efectes incorrectes. Tres diferències reals:
Primera: useEffect no s'executa en els mateixos instants. S'executa després que el navegador hagi pintat, mentre que componentDidMount ho fa abans. Per a la majoria dels casos és igual, però si necessites mesurar el DOM i ajustar el disseny sense parpelleig, hi ha un hook específic (useLayoutEffect, esmentat a 05-02).
Segona: un component pot tenir molts efectes, i una classe només tenia un componentDidMount. En una classe, la subscripció al temporitzador, la càrrega de dades i el registre d'analítica compartien mètode encara que no tinguessin res a veure. Amb hooks, cada preocupació va al seu propi useEffect, agrupada amb la seva neteja corresponent. S'organitza per assumpte, no per moment.
Tercera, i la que de veritat importa: el model mental canvia. La pregunta correcta ja no és «en quin moment del cicle de vida faig això?» sinó:
«Quin sistema extern ha de mantenir-se sincronitzat amb l'estat d'aquest component, i com es desincronitza quan el component deixa de necessitar-lo?»
Un efecte no és «codi que s'executa en muntar». És una descripció d'una sincronització: mentre aquest component existeixi amb aquestes props i aquest estat, hi ha d'haver un temporitzador actiu / una subscripció oberta / una connexió establerta; i quan això deixi de ser cert —perquè el component es desmunta o perquè canvien les dependències— la sincronització es desfà i, si escau, es refà amb els valors nous.
Aquest canvi de mentalitat explica un comportament que desconcerta qui ve de les classes: un efecte amb dependències pot executar la seva neteja i tornar-se a muntar moltes vegades durant la vida del component, no només al principi i al final. Amb el model de «moments del cicle de vida» això sembla un error; amb el model de sincronització és exactament el que ha de passar. Es desenvolupa a fons a useEffect.
| Model mental antic (classes) | Model mental correcte (React modern) |
|---|---|
| «Quan s'executa això?» | «Amb què ha d'estar sincronitzat això?» |
| El codi s'agrupa per moment | El codi s'agrupa per assumpte |
| Muntar i desmuntar són esdeveniments únics | Sincronitzar i desincronitzar es poden repetir |
| La neteja és lluny del que neteja | La neteja és al costat del que neteja |
| Un mètode per fase | Tants efectes com sistemes externs |
- Conviure amb classes en una base de codi real
Les classes no desapareixeran, i hi ha tres raons per a això:
- React manté la seva compatibilitat. No hi ha cap anunci d'eliminació: el codi amb classes seguirà funcionant.
- Migrar costa diners i no afegeix funcionalitat. Un component de classe que porta cinc anys funcionant sense errors no és una prioritat per a ningú.
- Els límits d'error encara exigeixen classe. És l'única peça de React 19 que no té equivalent amb hooks, i la veuràs a la propera lliçó.
Com treballar-hi sense patir:
- Les pots barrejar lliurement. Un component de funció pot renderitzar-ne un de classe i a l'inrevés, sense adaptadors. Ambdós reben props igual i participen en el mateix arbre.
- No converteixis per convertir. Migra un component de classe quan ja l'hagis de tocar per un altre motiu: un error, una funcionalitat nova, un refactor demanat. La migració oportunista té el millor equilibri entre risc i benefici.
- Prioritza pel dolor real. Els candidats clars són els components amb
bindper tot arreu, amb la mateixa lògica duplicada entrecomponentDidMounticomponentDidUpdate, o embolicats en diversos HOC (04-02). Allà la migració es paga sola. - Un component de classe no pot usar hooks. És una limitació absoluta: si necessites un hook dins d'una classe, no hi ha drecera, cal convertir el component.
- En migrar, comença per l'inventari. Enumera quins mètodes del cicle de vida usa el component, tradueix cadascun amb la taula de l'apartat 8 i revisa després la llista de dependències de cada efecte. Copiar només el cos de
renderés l'error que s'avisava a 02-02.
Errors Comuns i Consells
setStatesense comparació dins decomponentDidUpdate. Bucle infinit i «Maximum update depth exceeded». Compara semprepropsPreviesambthis.propsabans d'actualitzar.- Oblidar
componentWillUnmount. Temporitzadors, escoltadors i subscripcions que sobreviuen al component: fuita de memòria i avisos desetStatesobre components desmuntats. Per cada subscripció en muntar, una baixa en desmuntar. removeEventListeneramb una altra funció. Passar una fletxa nova no elimina res. Guarda la referència a la instància o enllaça-la al constructor.- Oblidar
super(props). Error immediat en usarthisal constructor. Sempre la primera línia. - Mutar l'estat directament.
this.state.hores = 5no dispara cap render i deixa la pantalla desactualitzada. Nomésthis.setState. - Confiar que
this.stateestà actualitzat just després desetState. No ho està: les actualitzacions s'agrupen. Usa la forma funcionalthis.setState((previ) => …)quan el valor nou depengui de l'anterior, exactament igual que ambuseState. - Guardar a l'estat dades que no es pinten. L'identificador d'un temporitzador va a
this, no athis.state; guardar-lo com a estat provoca renders inútils. - Consell: usa
console.logamb prefix per aprendre l'ordre. Posa un missatge a cada mètode, munta i desmunta el component, i observa la seqüència real a la consola. AmbStrictModeveuràs el doble muntatge de desenvolupament, que és una bona manera de comprovar que la teva neteja funciona. - Consell: no aprenguis React modern en termes de cicle de vida. Usa la taula per traduir codi antic, però raona els efectes nous en termes de sincronització.
Exercicis
Exercici 1. Aquest component de classe de CicloUrbano té tres errors relacionats amb el cicle de vida. Troba'ls, explica què provoca cadascun i corregeix-lo.
class ComptadorEstacions extends Component {
constructor(props) {
this.state = { visites: 0, ara: new Date() };
}
componentDidMount() {
setInterval(() => this.setState({ ara: new Date() }), 1000);
}
componentDidUpdate() {
this.setState({ visites: this.state.visites + 1 });
}
render() {
return <p>Visites: {this.state.visites} — {this.state.ara.toLocaleTimeString('ca-ES')}</p>;
}
}Exercici 2. Tradueix a component de funció amb hooks el següent RellotgeEstacio, que mostra l'hora d'una estació i es torna a subscriure quan canvia l'estació. No cal que dominis useEffect: guia't per la taula de l'apartat 8 i per l'exemple de l'apartat 7.
class RellotgeEstacio extends Component {
constructor(props) {
super(props);
this.state = { hora: new Date() };
this.actualitzar = this.actualitzar.bind(this);
}
componentDidMount() {
this.temporitzador = setInterval(this.actualitzar, 1000);
}
componentWillUnmount() {
clearInterval(this.temporitzador);
}
actualitzar() {
this.setState({ hora: new Date() });
}
render() {
return (
<p>
{this.props.estacio.nom}: {this.state.hora.toLocaleTimeString('ca-ES')}
</p>
);
}
}Exercici 3. Explica, per a cadascun d'aquests tres casos de CicloUrbano, en quin mètode del cicle de vida aniria el codi en una classe i amb quin hook es resoldria avui. Justifica la resposta en termes de sincronització, no de moments.
- Registrar en un servei d'analítica que s'ha visitat la fitxa d'una bicicleta, cada vegada que canvia la bicicleta mostrada.
- Obrir una connexió en temps real que informa de les places lliures de l'estació seleccionada.
- Calcular el preu total d'una reserva a partir de
preuHoraihores.
Solucions
Solució 1. Els tres errors:
- Falta
super(props)al constructor. JavaScript llança «Must call super constructor before accessing 'this'» i el component no arriba a muntar-se. - L'interval no es cancel·la mai: no hi ha
componentWillUnmountni es guarda l'identificador. Cada muntatge deixa un temporitzador viu per sempre. componentDidUpdatecridasetStatesense comparar: cada actualització en provoca una altra, en bucle infinit. I com que l'interval actualitza cada segon, el bucle arrencaria tot sol encara que ningú toqués res. Per comptar visites de veritat cal una dada amb què comparar; a la correcció suposem que el component rep una propestacioId.
class ComptadorEstacions extends Component {
constructor(props) {
super(props); // 1. corregit
this.state = { visites: 0, ara: new Date() };
}
componentDidMount() {
this.temporitzador = setInterval( // 2. guardem la referència
() => this.setState({ ara: new Date() }),
1000
);
}
componentDidUpdate(propsPrevies) {
if (propsPrevies.estacioId !== this.props.estacioId) { // 3. comparació
this.setState((previ) => ({ visites: previ.visites + 1 }));
}
}
componentWillUnmount() {
clearInterval(this.temporitzador); // 2. baixa del temporitzador
}
render() {
return <p>Visites: {this.state.visites} — {this.state.ara.toLocaleTimeString('ca-ES')}</p>;
}
}Fixa't a més en la forma funcional (previ) => ({ visites: previ.visites + 1 }): és la mateixa precaució que amb useState, perquè el valor nou depèn de l'anterior i les actualitzacions s'agrupen.
Solució 2.
// src/components/RellotgeEstacio.jsx
import { useState, useEffect } from 'react';
/**
* Props:
* - estacio (objecte Estacio, obligatori)
*/
function RellotgeEstacio({ estacio }) {
const [hora, setHora] = useState(new Date());
useEffect(() => {
const temporitzador = setInterval(() => setHora(new Date()), 1000);
return () => clearInterval(temporitzador);
}, []);
return (
<p>
{estacio.nom}: {hora.toLocaleTimeString('ca-ES')}
</p>
);
}
export default RellotgeEstacio;Traducció mètode a mètode: this.state = { hora } → useState(new Date()); componentDidMount → el cos de l'useEffect amb array buit; componentWillUnmount → la funció retornada per l'efecte; render → el return de la funció. Desapareixen el constructor, el bind, els quatre this i la classe sencera: de 24 línies a 12, i la neteja queda enganxada al que neteja.
Solució 3.
| Cas | Mètode de classe | Solució moderna | Raonament de sincronització |
|---|---|---|---|
| 1. Registrar la visita a una fitxa | componentDidMount + componentDidUpdate comparant bicicleta.id |
useEffect amb [bicicleta.id] a les dependències |
Hi ha un sistema extern (l'analítica) que s'ha d'assabentar cada vegada que canvia la bicicleta mostrada. La duplicació entre dos mètodes desapareix: un sol bloc cobreix el primer enviament i tots els següents |
| 2. Connexió en temps real de places lliures | componentDidMount per obrir, componentDidUpdate per tancar l'anterior i obrir la nova, componentWillUnmount per tancar |
useEffect amb [estacio.id] i una funció de neteja que tanca la connexió |
La connexió ha d'estar oberta mentre el component mostri aquella estació. En canviar d'estació, la sincronització es desfà (tanca) i es refà (obre) amb el valor nou. Això és exactament el que el model de «moments» no sap expressar |
| 3. Calcular el preu total | Cap | Cap hook: una variable calculada al cos de la funció | No hi ha cap sistema extern. És un valor derivat de props i estat, com bicicletesVisibles a 04-01. Ficar-ho en un efecte afegiria un render extra i una oportunitat de desincronització a canvi de res |
El tercer cas és el més instructiu: la meitat dels efectes innecessaris que s'escriuen a React són càlculs derivats disfressats de sincronització.
Conclusió
Els components de React neixen, s'actualitzen i moren, i aquestes tres fases es van gestionar durant anys amb els mètodes del cicle de vida dels components de classe: constructor i render per al muntatge, componentDidMount per al que necessita el DOM ja pintat, componentDidUpdate —amb la seva comparació obligatòria de props prèvies— per reaccionar als canvis, componentWillUnmount per donar de baixa el que es va donar d'alta i evitar fuites, shouldComponentUpdate per al rendiment i els mètodes UNSAFE_ com a recordatori del que no s'ha de fer. Ara ja saps llegir-los, corregir els seus errors clàssics i traduir-los amb la taula d'equivalències.
Però la conclusió important és la de l'apartat 9: l'equivalència és aproximada i el model mental és diferent. En React modern no es pensa en moments del cicle de vida, sinó en sincronitzar el component amb sistemes externs i desfer aquesta sincronització quan deixa de fer falta. Aquesta idea és la columna vertebral de useEffect.
I queda una pregunta natural: si els hooks substitueixen gairebé tot el que feien les classes, què són exactament, per què tenen regles tan estrictes sobre on es poden escriure i quants n'hi ha? La propera lliçó és Hooks: Introducció i Ús Bàsic, on veuràs el mecanisme intern que explica aquestes regles, el catàleg complet dels hooks que usaràs al curs i el teu primer component de CicloUrbano que combina estat i efecte.
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
