Tens el projecte de CicloUrbano creat i funcionant, però tot el que veus en pantalla és la plantilla d'exemple de Vite. És hora d'esborrar-la i escriure el primer codi veritablement teu. En aquesta lliçó desmuntaràs peça a peça el mecanisme que fa que React aparegui a la pàgina, entendràs la relació exacta entre index.html i src/main.jsx, i crearàs els teus dos primers components: Benvinguda i TargetaBicicleta. Quan acabis sabràs què és un component, com s'escriu, com es fa servir i per què el seu nom ha de començar obligatòriament per majúscula.
Contingut
- Netejar la plantilla d'exemple
- Anatomia de
index.html: el punt d'ancoratge src/main.jsxlínia a línia- El teu primer component:
Benvinguda - Un component és una funció que retorna JSX
- La convenció de la majúscula inicial
- Segon component:
TargetaBicicleta React.StrictMode: què és i per què veus coses dues vegades- Veure el canvi en calent
- Netejar la plantilla d'exemple
Vite genera una demostració amb un comptador i logotips. És útil per comprovar que tot funciona, però nosa per començar. Amb el servidor de desenvolupament en marxa (npm run dev), fes aquesta neteja.
Esborra aquests fitxers, que no farem servir:
Buida src/index.css i deixa'l amb uns estils base sobris per a CicloUrbano:
/* src/index.css — estils globals de CicloUrbano */
:root {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #1f2933;
background-color: #f5f7fa;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 2rem;
}
h1,
h2,
h3 {
line-height: 1.25;
margin-top: 0;
}Substitueix completament el contingut de src/App.jsx:
// src/App.jsx
function App() {
return (
<main>
<h1>CicloUrbano</h1>
</main>
);
}
export default App;Desa i mira el navegador: la demostració ha desaparegut i queda un títol. Si veus un error en pantalla o a la consola, gairebé segur és perquè App.jsx encara importa ./App.css o ./assets/react.svg, fitxers que acabes d'esborrar. Revisa que no quedi cap import sobrant.
Aquest App.jsx mínim ja conté tots els conceptes de la lliçó: una funció anomenada App, que retorna alguna cosa que sembla HTML, i que s'exporta. Anem a entendre cada part.
- Anatomia de
index.html: el punt d'ancoratge
index.html: el punt d'ancoratgeRecorda l'HTML del projecte, ja amb el títol de CicloUrbano:
<!doctype html>
<html lang="ca">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CicloUrbano — Lloguer de bicicletes urbanes</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>Això és el que passa quan algú obre la pàgina:
- El navegador descarrega aquest HTML. El
<body>conté un únicdivbuit. En aquest moment la pàgina està literalment en blanc. - Troba l'etiqueta
<script type="module">i descarrega/src/main.jsx(en desenvolupament el serveix Vite ja transformat; en producció serà el paquet generat pernpm run build). - Aquest script executa React, que crea els nodes del DOM i els insereix dins de
div#root. - La pàgina apareix.
D'aquí ve el nom de SPA (Single Page Application): existeix una única pàgina HTML real i tot el contingut el genera JavaScript dins d'un contenidor. L'id="root" no és màgic: podria dir-se app o ciclourbano sempre que main.jsx el busqui amb el mateix nom. root és la convenció universal, així que conserva-la.
sequenceDiagram
participant N as Navegador
participant H as index.html
participant M as main.jsx
participant R as React
N->>H: Sol·licita la pàgina
H-->>N: HTML amb div#root buit
N->>M: Carrega el mòdul /src/main.jsx
M->>R: createRoot(div#root)
R->>R: Executa el component App
R-->>N: Insereix els nodes dins de div#root
src/main.jsx línia a línia
src/main.jsx línia a líniaAquest és el fitxer complet:
// src/main.jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.jsx';
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>
);Cinc línies d'importació i una sentència. Anem una per una.
import { StrictMode } from 'react';
Del paquet react importem StrictMode, un component especial d'ajuda al desenvolupament. El veurem a l'apartat 8.
import { createRoot } from 'react-dom/client';
Aquí hi ha la separació de responsabilitats que vam mencionar a la lliçó 01-01: react sap descriure interfícies; react-dom sap pintar-les en un navegador. El subcamí /client indica que és el renderitzador de client (també existeix react-dom/server per a renderitzat en servidor, tema del Mòdul 10).
createRoot crea una arrel de React: la connexió entre un node del DOM real i l'arbre de components que React gestionarà dins d'ell.
Important:
createRootés l'API moderna, introduïda a React 18. En tutorials antics veuràsReactDOM.render(<App />, document.getElementById('root')). Aquesta API està eliminada a React 19 i no funcionarà. Fes servir semprecreateRoot.
import './index.css';
Importar un CSS des de JavaScript resulta xocant la primera vegada, però és una funcionalitat de l'eina de construcció, no de React: Vite veu l'import, processa el full d'estils i l'injecta a la pàgina. Com que no n'extraiem cap valor, no hi ha claus ni nom de variable.
import App from './App.jsx';
Importació per defecte: per això no porta claus. Es correspon amb l'export default App; del final d'App.jsx. En ser per defecte, el nom local és lliure —podries escriure import Arrel from './App.jsx'—, tot i que per claredat s'usa sempre el mateix.
La sentència final. Llegeix-la de dins cap a fora:
document.getElementById('root')és JavaScript del DOM de tota la vida: obté eldivbuit de l'HTML.createRoot(...)converteix aquestdiven una arrel gestionada per React i retorna un objecte amb un mètoderender..render(...)rep la descripció de la interfície i la pinta dins de l'arrel.<App />és el teu component usat com si fos una etiqueta. Fixa't en la barra final: els components sense contingut intern s'autotanquen, igual que<img />o<input />.
És l'única vegada en tota l'aplicació que cridaràs createRoot. A partir d'aquí, tota la resta són components dins de components.
- El teu primer component:
Benvinguda
BenvingudaCrea la carpeta on viuran els components de CicloUrbano i, a dins, el primer:
// src/components/Benvinguda.jsx
function Benvinguda() {
return (
<header>
<h1>CicloUrbano</h1>
<p>Lloga una bicicleta urbana al teu barri, per hores i sense complicacions.</p>
</header>
);
}
export default Benvinguda;Analitzem les quatre parts:
function Benvinguda(): és una funció de JavaScript normal i corrent. Sense paràmetres, de moment.return (...): retorna la descripció d'un tros d'interfície escrita en JSX. Els parèntesis després dereturnno són obligatoris quan el JSX cap en una línia, però sí que ho són a la pràctica quan n'ocupa diverses: sense ells, el punt i coma automàtic de JavaScript inseriria un;just després dereturni la funció retornariaundefined.- Un únic element arrel: tot el contingut està embolcallat en
<header>. React necessita retornar una sola cosa; aprofundiràs en aquesta regla i en els fragments a la lliçó JSX. export default Benvinguda;: fa que el component sigui importable des d'altres fitxers.
Ara fes-lo servir a App.jsx:
// src/App.jsx
import Benvinguda from './components/Benvinguda.jsx';
function App() {
return (
<main>
<Benvinguda />
</main>
);
}
export default App;Desa i mira el navegador: apareixen el títol i l'eslògan de CicloUrbano. Acabes d'escriure i fer servir el teu primer component de React.
- Un component és una funció que retorna JSX
Aquesta frase mereix que la interioritzis, perquè és la definició completa:
Un component de React és una funció de JavaScript el nom de la qual comença per majúscula i que retorna una descripció d'interfície (JSX).
No hi ha herència, ni classes, ni registre enlloc, ni configuració. Una funció. I com que és una funció, s'utilitza com una etiqueta:
| El que escrius | El que fa React |
|---|---|
<Benvinguda /> |
Crida la funció Benvinguda() i col·loca en aquell punt el que retorna |
<Benvinguda></Benvinguda> |
Exactament el mateix; la forma autotancada és l'habitual quan no hi ha contingut a dins |
Benvinguda() |
Crida la funció directament. No ho facis: React deixa de tractar-lo com un component i perd la seva identitat per a l'estat, DevTools i la reconciliació |
Pots escriure el mateix component amb funció fletxa; totes dues formes són equivalents i veuràs totes dues en projectes reals:
// Declaració de funció (la que farem servir al curs)
function Benvinguda() {
return <h1>CicloUrbano</h1>;
}
// Funció fletxa assignada a una constant
const Benvinguda = () => {
return <h1>CicloUrbano</h1>;
};
// Funció fletxa amb retorn implícit (sense claus ni return)
const Benvinguda = () => <h1>CicloUrbano</h1>;En aquest curs farem servir la declaració de funció perquè el seu nom apareix sempre de forma clara en els missatges d'error i a React DevTools, i perquè no depèn de la posició en el fitxer.
I com que els components són funcions que retornen descripcions d'interfície, es componen entre si formant un arbre:
flowchart TD
R["Arrel de React (div#root)"] --> S[StrictMode]
S --> A[App]
A --> B[Benvinguda]
A --> T[TargetaBicicleta]
- La convenció de la majúscula inicial
No és una preferència d'estil: és una regla obligatòria. JSX decideix què fer amb una etiqueta mirant la primera lletra del seu nom.
<header> // minúscula -> React entén: etiqueta HTML estàndard 'header'
<Benvinguda /> // majúscula -> React entén: el meu component anomenat BenvingudaLa raó és que JSX es transforma en crides a una funció, i en aquesta transformació la primera lletra determina si el primer argument és una cadena de text o una referència a la teva funció:
// Escrius això:
<header />
<Benvinguda />
// I es transforma, a grans trets, en això:
jsx('header', ...) // cadena: React crea un <header> del navegador
jsx(Benvinguda, ...) // referència: React executa la teva funcióSi escrius el teu component en minúscula, React buscarà una etiqueta HTML anomenada benvinguda, no la trobarà com a element conegut i el navegador la mostrarà buida. No veuràs cap error vermell, només una pàgina incompleta, cosa que converteix aquesta fallada en una de les més frustrants per a qui comença.
| Nom | És vàlid? | Resultat |
|---|---|---|
Benvinguda |
Sí | Component correcte |
TargetaBicicleta |
Sí | Correcte; PascalCase per a noms compostos |
benvinguda |
No | React busca una etiqueta HTML inexistent; no es pinta res |
targeta_bicicleta |
No | Mateix problema, a més de trencar la convenció de noms |
Regla pràctica: components en PascalCase, fitxers amb el mateix nom que el component. Benvinguda viu a Benvinguda.jsx, TargetaBicicleta a TargetaBicicleta.jsx. Així trobar el codi del que veus en pantalla és immediat.
- Segon component:
TargetaBicicleta
TargetaBicicletaAnem a crear la peça més característica de CicloUrbano: la targeta que representa una bicicleta. En aquesta lliçó les dades van escrites a mà dins del component; fer-la flexible amb dades que arriben de fora és l'objectiu de la lliçó Props.
// src/components/TargetaBicicleta.jsx
function TargetaBicicleta() {
return (
<article className="targeta-bicicleta">
<h3>Urbana Clàssica</h3>
<p>Tipus: urbana</p>
<p>Estat: disponible</p>
<p>Estació: Plaça Major</p>
<p>
<strong>2,50 € / hora</strong>
</p>
</article>
);
}
export default TargetaBicicleta;Dos detalls que potser t'han cridat l'atenció:
classNameen lloc declass.classés una paraula reservada de JavaScript, i JSX és JavaScript. Per això React fa servirclassName. És l'error de novell més freqüent i el veurem a fons a la propera lliçó.<article>i<h3>en lloc dedivper a tot. React no t'allibera d'escriure HTML semàntic: els components acaben produint etiquetes reals, i triar-les bé beneficia l'accessibilitat i el SEO.
Les dades corresponen a la bicicleta bici-001 del domini de CicloUrbano que vam fixar a la lliçó 01-01.
Ara fes-lo servir, i de passada comprova una cosa important: el mateix component es pot fer servir tantes vegades com vulguis.
// src/App.jsx
import Benvinguda from './components/Benvinguda.jsx';
import TargetaBicicleta from './components/TargetaBicicleta.jsx';
function App() {
return (
<main>
<Benvinguda />
<section>
<h2>Bicicletes destacades</h2>
<TargetaBicicleta />
<TargetaBicicleta />
<TargetaBicicleta />
</section>
</main>
);
}
export default App;Al navegador veuràs tres targetes idèntiques. Que siguin idèntiques és precisament la limitació que resoldrem amb props: cada instància rebrà la seva pròpia bicicleta i mostrarà dades diferents. Però el mecanisme de reutilització ja és a la vista, i és l'essència del model de components.
Afegeix uns estils a src/index.css perquè es vegi presentable:
/* Afegir al final de src/index.css */
.targeta-bicicleta {
background: #ffffff;
border: 1px solid #d9e2ec;
border-radius: 8px;
padding: 1rem 1.25rem;
margin-bottom: 1rem;
max-width: 22rem;
}
.targeta-bicicleta h3 {
margin-bottom: 0.5rem;
color: #12805c;
}
.targeta-bicicleta p {
margin: 0.25rem 0;
font-size: 0.95rem;
}
React.StrictMode: què és i per què veus coses dues vegades
React.StrictMode: què és i per què veus coses dues vegadesTorna a main.jsx i fixa't que <App /> està embolcallat en <StrictMode>:
StrictMode és un component especial que no pinta absolutament res: no genera cap etiqueta al DOM. La seva única funció és activar comprovacions addicionals durant el desenvolupament per detectar codi problemàtic com més aviat millor.
El que fa:
- Executa els teus components dues vegades a cada render (només en desenvolupament). Serveix per descobrir efectes secundaris accidentals: si el teu component modifica una variable externa o fa alguna cosa més que calcular la seva interfície, amb dues execucions el resultat serà diferent i la fallada sortirà a la llum de seguida en lloc de d'aquí a sis mesos.
- Munta, desmunta i torna a muntar cada component a l'inici, per verificar que la neteja d'efectes està ben escrita.
- Avisa d'API obsoletes i de patrons desaconsellats.
D'aquí la conseqüència que més desconcerta qui comença: si poses un console.log('renderitzant') dins d'un component, veuràs el missatge dues vegades a la consola. No és una fallada teva ni de React.
Comprova-ho:
// src/components/Benvinguda.jsx
function Benvinguda() {
console.log("S'està executant Benvinguda");
return (
<header>
<h1>CicloUrbano</h1>
<p>Lloga una bicicleta urbana al teu barri, per hores i sense complicacions.</p>
</header>
);
}
export default Benvinguda;La consola del navegador mostra el missatge dues vegades. Recorda:
Amb StrictMode (desenvolupament) |
En producció | |
|---|---|---|
| Execucions del component | Dobles, a propòsit | Una de sola |
| Efectes en muntar | S'executen, netegen i repeteixen | Una sola vegada |
| Impacte en l'usuari final | Cap: només afecta npm run dev |
Cap |
No l'elimines. La temptació d'eliminar StrictMode perquè la consola quedi neta és gran i és un error: estaries desactivant el detector de fallades precisament on serveix. La duplicació d'efectes i la seva relació amb useEffect es retoma amb detall a la lliçó Hook useEffect.
Esborra ja el console.log abans de continuar.
- Veure el canvi en calent
Amb tot muntat, aprofita per experimentar i consolidar el que has après. Amb npm run dev en marxa i el navegador al costat de l'editor:
- Canvia l'eslògan de
Benvingudai desa. El text s'actualitza a l'instant, sense recarregar. - Canvia el model de
TargetaBicicletaaElèctrica Proi el preu a4,00 € / hora. Les tres targetes canvien alhora, perquè totes tres són la mateixa funció retornant el mateix. - Obre React DevTools, pestanya Components. Veuràs l'arbre
App → Benvingudai tresTargetaBicicleta. Observa queStrictModeapareix a l'arbre de React però no a la pestanya Elements del navegador: confirma que no genera HTML. - Comenta una de les tres
<TargetaBicicleta />amb{/* ... */}i comprova que desapareix.
Aquest bucle —editar, desar, veure el resultat— és el ritme de treball normal en React, i és la raó per la qual l'HMR de Vite importa tant.
Errors habituals i consells
- Oblidar l'
export default. L'import retornaundefinedi React llançaElement type is invalid: expected a string ... but got: undefined. Sempre que vegis aquest missatge, revisa la parella export/import del component esmentat. - Confondre export per defecte i export amb nom.
export default Benvingudas'importa comimport Benvinguda from ...;export function Benvingudas'importa comimport { Benvinguda } from .... Barrejar-los donaundefined. - Anomenar el component en minúscula.
<benvinguda />no dona cap error visible: simplement no pinta res. Si un component «no apareix» i la consola està neta, comprova la majúscula inicial abans que res. - Posar el
returnen una línia i el JSX a la següent sense parèntesis. La inserció automàtica de punt i coma fa que la funció retorniundefinedi React es queixi que no s'ha retornat res. Obre el parèntesi a la mateixa línia delreturn. - Fer servir
ReactDOM.render. Està eliminat a React 19. Si un tutorial l'usa, està desactualitzat; tradueix-ho acreateRoot(...).render(...). - Cridar el component com a funció,
{Benvinguda()}. Funciona visualment, però React perd la identitat del component: no apareix a DevTools, no pot tenir el seu propi estat ni participar bé en la reconciliació. Fes servir sempre<Benvinguda />. - Eliminar
StrictModeperquè «la consola surt doble». És el comportament esperat en desenvolupament i desactivar-lo amaga fallades reals. - Consell: un component per fitxer, amb el mateix nom que el fitxer, a
src/components/. Quan CicloUrbano tingui trenta components ho agrairàs.
Exercicis
Exercici 1
Crea un component PeuDePagina a src/components/PeuDePagina.jsx que mostri, dins d'una etiqueta <footer>, el text «CicloUrbano — Servei de lloguer de bicicletes urbanes» i, en una segona línia, «Estacions: Plaça Major · Parc Nord · Estació Central». Fes-lo servir a App.jsx a sota de les targetes.
Exercici 2
Aquest codi no funciona. Localitza els tres errors i corregeix-los explicant per què falla cadascun.
// src/components/estacio.jsx
function estacio() {
return
<div class="estacio">
<h3>Plaça Major</h3>
<p>Barri Centre · 20 places</p>
</div>;
}
export estacio;Exercici 3
Crea un component TargetaEstacio que mostri les dades de l'estació est-02 del domini de CicloUrbano (Parc Nord, barri Nord, 15 places) amb dades escrites a mà, i munta'l a App.jsx dins d'una <section> amb l'encapçalament «Estacions». Després respon: si volguessis mostrar les tres estacions del domini amb aquest mateix component, quina limitació et trobaries i a quina lliçó del curs es resol?
Solucions
Solució 1.
// src/components/PeuDePagina.jsx
function PeuDePagina() {
return (
<footer>
<p>CicloUrbano — Servei de lloguer de bicicletes urbanes</p>
<p>Estacions: Plaça Major · Parc Nord · Estació Central</p>
</footer>
);
}
export default PeuDePagina;// src/App.jsx
import Benvinguda from './components/Benvinguda.jsx';
import TargetaBicicleta from './components/TargetaBicicleta.jsx';
import PeuDePagina from './components/PeuDePagina.jsx';
function App() {
return (
<main>
<Benvinguda />
<section>
<h2>Bicicletes destacades</h2>
<TargetaBicicleta />
<TargetaBicicleta />
<TargetaBicicleta />
</section>
<PeuDePagina />
</main>
);
}
export default App;Solució 2. Els tres errors:
- Nom en minúscula.
function estacio()fa que<estacio />s'interpreti com una etiqueta HTML desconeguda i no es pinti res. Ha de serTargetaEstacio(PascalCase), i per coherència el fitxer ha de dir-se igual. returnseguit de salt de línia sense parèntesis. JavaScript insereix un punt i coma automàticament després dereturn, la funció retornaundefinedi React protesta. Cal obrir el parèntesi a la mateixa línia delreturn.classen comptes declassName.classés paraula reservada de JavaScript. En JSX s'escriuclassName. (Iexport estacio;tampoc és sintaxi vàlida: hi faltadefault.)
// src/components/TargetaEstacio.jsx
function TargetaEstacio() {
return (
<div className="estacio">
<h3>Plaça Major</h3>
<p>Barri Centre · 20 places</p>
</div>
);
}
export default TargetaEstacio;Solució 3.
// src/components/TargetaEstacio.jsx
function TargetaEstacio() {
return (
<article className="targeta-estacio">
<h3>Parc Nord</h3>
<p>Barri: Nord</p>
<p>Places: 15</p>
</article>
);
}
export default TargetaEstacio;La limitació: les dades estan escrites dins del component, així que les tres instàncies mostrarien exactament «Parc Nord, Nord, 15 places». Perquè cada targeta mostri una estació diferent, el component ha de rebre les dades des de fora. Aquest mecanisme són les props, i s'estudia a la lliçó Props: passant dades als components. Pintar una llista completa a partir d'un array es veurà a Llistes i claus.
Conclusió
Has passat d'executar la plantilla de Vite a escriure els teus propis components. Ara saps que index.html aporta un div#root buit, que main.jsx el converteix en arrel de React amb createRoot(...).render(...) —mai amb l'obsolet ReactDOM.render—, i que un component no és més que una funció amb nom en majúscula que retorna JSX i s'utilitza com una etiqueta. Has creat Benvinguda i TargetaBicicleta, has comprovat que un mateix component es reutilitza tantes vegades com calgui i entens per què StrictMode executa el teu codi dues vegades en desenvolupament.
Tot el que has escrit dins del return és JSX, i fins ara l'has fet servir per intuïció: sembla HTML i funciona gairebé com HTML. Gairebé. A la lliçó següent, JSX: extensió de sintaxi de JavaScript, veuràs en què es transforma realment, quines regles el separen de l'HTML i com inserir valors dinàmics amb claus perquè les teves targetes deixin de ser text fix.
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
