A la primera lliçó del mòdul vam dir que un component encapsula tres coses: marcatge, lògica i estil. Les dues primeres ja estan resoltes: el marcatge viu al JSX i la lògica al cos de la funció. L'estil, en canvi, continua amuntegat en un únic src/index.css que creix sense control i on res no impedeix que d'aquí a tres mesos dues persones diferents defineixin una classe .targeta amb regles incompatibles. React no imposa cap solució per a això: t'ofereix diverses estratègies i espera que triïs amb criteri. En aquesta lliçó veuràs les quatre que s'usen a la pràctica, totes aplicades al mateix component —TargetaBicicleta— per poder-les comparar de debò, aprendràs a construir noms de classe dinàmics a partir de l'estat d'una bicicleta, ordenaràs la paleta de CicloUrbano amb variables CSS i adoptaràs l'estratègia que el projecte usarà d'ara endavant.
Contingut
- El problema: l'estil també és part del component
- Estratègia 1: CSS global importat
- Estratègia 2: estils en línia amb objectes
- Estratègia 3: CSS Modules amb Vite
- Estratègia 4: CSS-in-JS i frameworks d'utilitats
- Classes condicionals segons l'estat de la bicicleta
- Variables CSS per a la paleta de CicloUrbano
- Comparativa final i decisió del curs
- El problema: l'estil també és part del component
Avui TargetaBicicleta és incompleta com a unitat: el seu marcatge i la seva lògica són a TargetaBicicleta.jsx, però les seves regles d'estil són a cent línies de distància, en un fitxer que comparteix amb tota la resta. Això produeix quatre problemes concrets:
| Problema | Com es manifesta |
|---|---|
| Col·lisions de noms | Tot el CSS viu en un espai global. Una classe .targeta a dos llocs es trepitja; guanya la que es carregui després |
| Estils orfes | En esborrar un component, ningú es recorda d'esborrar el seu CSS. El fitxer global només creix |
| Por de modificar | Puc canviar .estat? Ningú sap quants components la fan servir. S'acaba afegint .estat-nou en lloc de tocar l'existent |
| Sense dinamisme | El CSS no coneix les dades. Pintar la vora del color del tipus de bicicleta exigeix que el component hi participi |
React no resol això per tu, però sí que et dona l'ingredient que faltava: el component pot decidir les seves classes en temps d'execució, perquè className és una expressió JavaScript com qualsevol altra.
Les quatre estratègies que existeixen es poden ordenar per on viu l'estil:
flowchart TD
A["On viu l'estil?"] --> B["En un .css global<br/>(Estratègia 1)"]
A --> C["En un objecte JS dins del JSX<br/>(Estratègia 2)"]
A --> D["En un .module.css al costat del component<br/>(Estratègia 3)"]
A --> E["Al mateix JSX com a classes utilitàries<br/>o generat per JS (Estratègia 4)"]
- Estratègia 1: CSS global importat
És la que has usat fins ara, i consisteix a escriure CSS normal i importar-lo.
/* 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;
}// src/components/TargetaBicicleta.jsx
function TargetaBicicleta({ bicicleta }) {
return (
<article className="targeta-bicicleta">
<h3>{bicicleta.model}</h3>
</article>
);
}Vite també permet importar un .css des del propi component, cosa que millora una mica l'organització:
// src/components/TargetaBicicleta.jsx
import './TargetaBicicleta.css'; // el fitxer viu al costat del componentPerò compte: això no el fa local. Vite recull tots els .css importats i els ajunta en el full d'estils de la pàgina. El fitxer és més a prop del component, sí, però les seves classes continuen sent globals.
El problema de les col·lisions, amb un exemple real
Imagina que d'aquí a dos mesos algú afegeix una pantalla de reserves amb el seu propi full:
/* src/components/TargetaReserva.css */
.targeta-bicicleta { /* reutilitza el nom "perquè s'hi assembla" */
max-width: 100%;
border: none;
background: #f5f7fa;
}Aquest fitxer s'importa i, sense que ningú ho pretengui, les targetes del catàleg perden la vora i l'amplada màxima. No hi ha error, no hi ha avís: només un disseny trencat que apareix en navegar en cert ordre. Depurar-ho ocupa mitja tarda.
Les mitigacions tradicionals existeixen i funcionen a mitges:
- Convencions de noms tipus BEM:
.targeta-bicicleta__titol,.targeta-bicicleta--urbana. Redueixen molt el risc, però depenen de la disciplina de tot l'equip, per sempre. - Prefixos per component: la mateixa idea, amb la mateixa fragilitat.
| Avantatges | Inconvenients |
|---|---|
| CSS pur, sense res a aprendre | Espai de noms global: col·lisions garantides a llarg termini |
| Funciona en qualsevol projecte | L'estil no viatja amb el component |
| Ideal per a reinicis, tipografia i variables | Ningú s'atreveix a esborrar regles: el fitxer només creix |
Quan usar-lo: per al que ha de ser global. Reinicis (box-sizing), tipografia base, colors del body i, sobretot, les variables CSS de la paleta, com veuràs a l'apartat 7.
- Estratègia 2: estils en línia amb objectes
React permet passar un objecte JavaScript a l'atribut style:
function TargetaBicicleta({ bicicleta }) {
return (
<article
style={{
background: '#ffffff',
border: '1px solid #d9e2ec',
borderRadius: '8px',
padding: '1rem 1.25rem',
maxWidth: '22rem'
}}
>
<h3 style={{ color: '#12805c', marginBottom: '0.5rem' }}>{bicicleta.model}</h3>
</article>
);
}Recorda de la lliçó 01-04 les regles d'aquest objecte:
- Doble clau: les de fora obren l'expressió JSX, les de dins són el literal d'objecte.
- Propietats en camelCase:
borderRadius, noborder-radius. - Valors com a cadenes, amb unitats incloses. Els números sense unitat s'interpreten com a píxels:
padding: 16equival a16px.
On brillen: valors calculats en temps d'execució
Aquest és el seu cas legítim, i és real. Una barra d'ocupació d'una estació l'amplada de la qual depèn de les dades no es pot expressar amb una classe CSS, perquè el percentatge només es coneix en executar:
// src/components/DisponibilitatEstacio.jsx
/**
* Barra d'ocupació d'una estació.
* Props:
* - estacio (objecte, obligatori) { id, nom, barri, places }
* - ocupades (número, opcional, per defecte 0)
*/
function DisponibilitatEstacio({ estacio, ocupades = 0 }) {
const percentatge = Math.round((ocupades / estacio.places) * 100);
return (
<div className="disponibilitat-estacio">
<p>
{estacio.nom}: {ocupades} de {estacio.places} places ocupades
</p>
<div className="disponibilitat-estacio__barra">
{/* L'amplada només es coneix en execució: cas legítim d'estil en línia */}
<div
className="disponibilitat-estacio__ompliment"
style={{ width: `${percentatge}%` }}
/>
</div>
</div>
);
}
export default DisponibilitatEstacio;Aquí la divisió del treball és la correcta: la classe aporta tot allò estàtic (colors, alçada, vores) i l'estil en línia aporta l'únic valor que depèn de les dades.
Els seus límits, que són severs
| Limitació | Conseqüència |
|---|---|
| Sense pseudoclasses | No hi ha :hover, :focus, :active, :disabled |
| Sense pseudoelements | No hi ha ::before ni ::after |
| Sense media queries | No hi ha disseny adaptable |
| Sense selectors descendents | Cal estilar cada node un per un |
| Sense animacions | No hi ha @keyframes |
| Especificitat màxima | Un estil en línia guanya a gairebé tot; sobreescriure'l obliga a !important |
| Objecte nou a cada render | El literal {{…}} crea un objecte diferent cada vegada, amb implicacions per a les optimitzacions del Mòdul 8 |
| Sense memòria cau ni compressió | L'estil viatja al JavaScript, no en un full cacheable |
Quan usar-lo: només per a valors dinàmics que el CSS no pot conèixer —amplades percentuals, posicions, transformacions calculades— i sempre combinat amb una classe que aporti la resta. Mai com a estratègia principal.
- Estratègia 3: CSS Modules amb Vite
Un CSS Module és un fitxer CSS normal el nom del qual acaba en .module.css. Vite el detecta automàticament —sense cap configuració— i fa dues coses: renombra cada classe perquè sigui única en tota l'aplicació i et retorna un objecte que tradueix els teus noms als generats.
Com s'escriu
/* src/components/TargetaBicicleta.module.css */
.targeta {
background: #ffffff;
border: 1px solid #d9e2ec;
border-radius: 8px;
padding: 1rem 1.25rem;
margin-bottom: 1rem;
max-width: 22rem;
border-left: 4px solid #d9e2ec;
}
.titol {
margin: 0 0 0.5rem;
color: #12805c;
display: flex;
align-items: center;
gap: 0.5rem;
}
.dada {
margin: 0.25rem 0;
font-size: 0.95rem;
}
.preu {
margin-top: 0.75rem;
font-weight: 700;
}
/* Variants per tipus de bicicleta */
.urbana { border-left-color: #12805c; }
.electrica { border-left-color: #b45309; }
.carga { border-left-color: #9b1c1c; }
/* Aquí SÍ funcionen pseudoclasses, medias i tota la resta */
.targeta:hover {
box-shadow: 0 2px 8px rgba(31, 41, 51, 0.12);
}
@media (max-width: 30rem) {
.targeta {
max-width: 100%;
}
}Fixa't en els noms: .targeta, .titol, .dada. Curts i genèrics, perquè l'àmbit local fa innecessari el prefix. No hi ha cap risc que xoquin amb les .targeta d'un altre component.
Com s'usa
// src/components/TargetaBicicleta.jsx
import estils from './TargetaBicicleta.module.css';
import EtiquetaEstat from './EtiquetaEstat.jsx';
function TargetaBicicleta({ bicicleta, nomEstacio = 'Estació desconeguda' }) {
const preuFormatat = bicicleta.preuHora.toFixed(2).replace('.', ',');
return (
<article className={`${estils.targeta} ${estils[bicicleta.tipus]}`}>
<h3 className={estils.titol}>
{bicicleta.model} <EtiquetaEstat estat={bicicleta.estat} />
</h3>
<p className={estils.dada}>Tipus: {bicicleta.tipus}</p>
<p className={estils.dada}>Estació: {nomEstacio}</p>
<p className={estils.preu}>{preuFormatat} € / hora</p>
</article>
);
}
export default TargetaBicicleta;Què passa per sota
import estils from './TargetaBicicleta.module.css' no importa text: importa un objecte que Vite genera en processar el fitxer.
// Contingut aproximat de l'objecte 'estils' en desenvolupament
{
targeta: '_targeta_1x9k2_1',
titol: '_titol_1x9k2_11',
dada: '_dada_1x9k2_18',
preu: '_preu_1x9k2_23',
urbana: '_urbana_1x9k2_28',
electrica: '_electrica_1x9k2_29',
carga: '_carga_1x9k2_30'
}I l'HTML que arriba al navegador:
<article class="_targeta_1x9k2_1 _urbana_1x9k2_28">
<h3 class="_titol_1x9k2_11">Urbana Clàssica <span class="estat estat--disponible">disponible</span></h3>
…
</article>Aquest sufix es calcula a partir del nom del fitxer i del seu contingut, de manera que és impossible que dos components diferents generin la mateixa classe. El problema de les col·lisions desapareix per construcció, no per disciplina.
Dos detalls de sintaxi útils:
estils[bicicleta.tipus]: accés per claudàtors. Com quebicicleta.tipusval'urbana','electrica'o'carga', obtenim la classe de la variant corresponent. Així es connecten les dades amb l'estil sense capif.- Classes amb guions: si anomenes una classe
.targeta-destacada, en JS hauràs d'escriureestils['targeta-destacada']. Per això a CSS Modules s'estila usar camelCase:.targetaDestacada→estils.targetaDestacada.
Avantatges i inconvenients
| Avantatges | Inconvenients |
|---|---|
| Àmbit local garantit per l'eina | Un fitxer extra per component |
| És CSS de debò: pseudoclasses, medias, animacions, tot | Els noms generats són il·legibles en inspeccionar (configurable) |
| L'estil viatja amb el component: s'esborra junt amb ell | Els valors dinàmics continuen necessitant estils en línia o variables CSS |
| Sense dependències: Vite el porta de sèrie | Cal recordar estils. davant de cada classe |
| El navegador pot cachejar el full resultant | Compartir estils entre components requereix pensar-hi (composes o un mòdul comú) |
Aquesta és l'estratègia per defecte de CicloUrbano a partir d'ara, per tres raons: no afegeix cap dependència, resol el problema real (les col·lisions) sense demanar-te disciplina, i continua sent CSS, de manera que tot el que saps de la cascada et serveix igual.
Estils compartits entre mòduls
Quan dos components necessiten les mateixes regles, hi ha dues vies netes:
/* src/estils/comuns.module.css */
.caixa {
background: #ffffff;
border: 1px solid #d9e2ec;
border-radius: 8px;
padding: 1rem 1.25rem;
}/* src/components/TargetaBicicleta.module.css */
.targeta {
composes: caixa from '../estils/comuns.module.css';
margin-bottom: 1rem;
max-width: 22rem;
}composes és una funcionalitat pròpia de CSS Modules: la classe resultant inclou totes dues. L'alternativa, més senzilla, és posar aquestes regles comunes al CSS global i combinar-les amb el helper de l'apartat 6: className={classes('caixa', estils.targeta)}.
- Estratègia 4: CSS-in-JS i frameworks d'utilitats
Les dues alternatives restants són molt populars i convé conèixer-les encara que no les fem servir al curs.
CSS-in-JS
Llibreries com styled-components o Emotion permeten escriure CSS dins de JavaScript i obtenir un component ja estilat:
// Exemple amb styled-components (NO s'usa en aquest curs)
import styled from 'styled-components';
const Targeta = styled.article`
background: #ffffff;
border: 1px solid #d9e2ec;
border-radius: 8px;
padding: 1rem 1.25rem;
/* Les props arriben a la plantilla: dinamisme total */
border-left: 4px solid ${props =>
props.$tipo === 'electrica' ? '#b45309' : '#12805c'};
&:hover {
box-shadow: 0 2px 8px rgba(31, 41, 51, 0.12);
}
`;
<Targeta $tipo={bicicleta.tipus}>…</Targeta>El seu gran atractiu és que les props determinen l'estil de forma directa, amb tota la potència de CSS. Els seus inconvenients: una dependència més, un cost en temps d'execució (els estils es generen mentre l'aplicació corre), fricció amb el renderitzat en servidor i una tendència clara de l'ecosistema a allunyar-se d'aquest enfocament en favor de solucions que resolen en temps de compilació (zero-runtime), com Vanilla Extract o Panda CSS.
Frameworks d'utilitats (Tailwind CSS)
L'enfocament oposat: en lloc d'escriure CSS, es componen classes atòmiques predefinides directament al JSX.
// Exemple amb Tailwind CSS (NO s'usa en aquest curs)
<article className="bg-white border border-slate-200 rounded-lg p-4 mb-4 max-w-sm border-l-4 border-l-emerald-700 hover:shadow-md">
<h3 className="text-emerald-700 font-bold mb-2">{bicicleta.model}</h3>
</article>Avantatges: no es canvia de fitxer per estilar, el CSS final és minúscul perquè només s'inclouen les utilitats usades, i el sistema de disseny (espaiats, colors, tipografies) ve imposat, cosa que dona molta consistència. Inconvenients: el JSX s'omple de classes i perd llegibilitat, hi ha una corba d'aprenentatge de noms, i compondre condicionals llargs exigeix una llibreria auxiliar.
Les quatre, en perspectiva
| Global | En línia | CSS Modules | CSS-in-JS | Utilitats | |
|---|---|---|---|---|---|
| Àmbit | Global | De l'element | Local | Local | Global (atòmic) |
| Pseudoclasses i medias | Sí | No | Sí | Sí | Sí |
| Dinamisme per props | No | Total | Per classes o variables | Total | Per classes |
| Dependències | Cap | Cap | Cap (Vite) | Una llibreria | Una eina |
| Cost en execució | Nul | Baix | Nul | Mitjà | Nul |
- Classes condicionals segons l'estat de la bicicleta
Sigui quina sigui l'estratègia, hi ha una necessitat constant: triar classes en funció de les dades. A CicloUrbano, el distintiu d'estat ha de ser verd si la bicicleta està disponible, taronja si està llogada i vermell si està en manteniment.
Amb plantilles de cadena
La forma més directa, i la que ja vas usar a EtiquetaEstat:
// src/components/EtiquetaEstat.jsx
function EtiquetaEstat({ estat = 'disponible' }) {
return <span className={`estat estat--${estat}`}>{estat}</span>;
}estat--${estat} produeix estat--disponible, estat--alquilada o estat--mantenimiento. Una sola línia cobreix els tres casos i afegir un quart estat no exigeix tocar el component, només el CSS.
Compte amb un límit important: això funciona amb CSS global, però no amb CSS Modules, perquè les classes d'un mòdul estan renombrades. Aquí cal accedir a l'objecte:
import estils from './EtiquetaEstat.module.css';
function EtiquetaEstat({ estat = 'disponible' }) {
return <span className={`${estils.etiqueta} ${estils[estat]}`}>{estat}</span>;
}Amb condicionals
Quan la classe depèn d'un booleà:
// Ternari: una condició
<article className={destacada ? 'targeta-bicicleta destacada' : 'targeta-bicicleta'}>
// Amb && dins de la plantilla: compte, si és false insereix "false"
<article className={`targeta-bicicleta ${destacada ? 'destacada' : ''}`}>Això es torna il·legible de seguida. Amb tres o quatre condicions apareixen plantilles monstruoses, espais dobles i undefined colant-se a l'atribut.
Un petit helper
La solució és una funció que ajunti els noms vàlids i descarti la resta. Ocupa tres línies i la faràs servir a tots els teus projectes:
// src/utilitats/classes.js
/**
* Uneix noms de classe descartant els valors falsy.
* classes('targeta', false, undefined, 'activa') -> 'targeta activa'
*/
export function classes(...noms) {
return noms.filter(Boolean).join(' ');
}Com funciona, pas a pas:
...nomsés un paràmetre rest: recull tots els arguments en un array..filter(Boolean)elimina els valors falsy de l'array:false,undefined,null,''i0. És una drecera idiomàtica equivalent a.filter(valor => Boolean(valor))..join(' ')uneix els que queden separats per espais, que és justament el format declassName.
Amb ell, el JSX torna a ser llegible:
// src/components/TargetaBicicleta.jsx
import { classes } from '../utilitats/classes.js';
import estils from './TargetaBicicleta.module.css';
import EtiquetaEstat from './EtiquetaEstat.jsx';
function TargetaBicicleta({ bicicleta, nomEstacio = 'Estació desconeguda', destacada = false }) {
const preuFormatat = bicicleta.preuHora.toFixed(2).replace('.', ',');
const noDisponible = bicicleta.estat !== 'disponible';
return (
<article
className={classes(
estils.targeta,
estils[bicicleta.tipus],
destacada && estils.destacada,
noDisponible && estils.atenuada
)}
>
<h3 className={estils.titol}>
{bicicleta.model} <EtiquetaEstat estat={bicicleta.estat} />
</h3>
<p className={estils.dada}>Tipus: {bicicleta.tipus}</p>
<p className={estils.dada}>Estació: {nomEstacio}</p>
<p className={estils.preu}>{preuFormatat} € / hora</p>
</article>
);
}
export default TargetaBicicleta;Si destacada és false, l'expressió destacada && estils.destacada val false, i filter(Boolean) la descarta. Sense ternaris, sense cadenes buides, sense espais sobrants.
En projectes reals veuràs la llibreria clsx (o classnames), que fa exactament això amb més funcionalitats. El nostre helper de tres línies cobreix el cas habitual sense afegir dependències.
I les classes que falten al mòdul:
/* Afegir a src/components/TargetaBicicleta.module.css */
.destacada {
box-shadow: 0 0 0 2px #12805c;
}
.atenuada {
opacity: 0.7;
}
- Variables CSS per a la paleta de CicloUrbano
Els colors de CicloUrbano es repeteixen literalment des de la lliçó 01-03: #12805c a cinc llocs, #d9e2ec en uns altres tants. Canviar el verd de marca implicaria avui una cerca i substitució per tot el projecte, amb el risc de deixar-se'n algun.
Les variables CSS (propietats personalitzades) resolen això de forma nativa, sense eines ni preprocessadors:
/* src/index.css — al principi del fitxer */
:root {
/* Paleta de marca */
--color-marca: #12805c;
--color-llogada: #b45309;
--color-manteniment: #9b1c1c;
/* Superfícies i text */
--color-fons: #f5f7fa;
--color-superficie: #ffffff;
--color-text: #1f2933;
--color-vora: #d9e2ec;
/* Formes i espaiat */
--radi: 8px;
--ombra: 0 2px 8px rgba(31, 41, 51, 0.12);
--espai: 1rem;
}I ara qualsevol fitxer d'estil del projecte les pot fer servir, inclosos els .module.css, perquè les variables sí que travessen l'àmbit local: el que CSS Modules renomena són les classes, no les propietats personalitzades.
/* src/components/TargetaBicicleta.module.css */
.targeta {
background: var(--color-superficie);
border: 1px solid var(--color-vora);
border-radius: var(--radi);
padding: var(--espai) 1.25rem;
border-left: 4px solid var(--color-vora);
}
.targeta:hover {
box-shadow: var(--ombra);
}
.titol {
color: var(--color-marca);
}
.urbana { border-left-color: var(--color-marca); }
.electrica { border-left-color: var(--color-llogada); }
.carga { border-left-color: var(--color-manteniment); }Avantatges concrets:
| Avantatge | Exemple |
|---|---|
| Una única font de veritat | Canviar --color-marca repinta tota l'aplicació |
| Es poden canviar en execució | Un tema fosc és qüestió de redefinir les variables sota un altre selector |
| Funcionen amb CSS Modules | L'àmbit local no afecta les propietats personalitzades |
| Pont perfecte amb JS | Un estil en línia pot fixar una variable, i el CSS la consumeix |
Aquest últim punt mereix un exemple, perquè combina el millor de les estratègies 2 i 3:
// El component calcula el valor i l'entrega com a variable CSS
<div
className={estils.ompliment}
style={{ '--amplada-ocupacio': `${percentatge}%` }}
/>.ompliment {
width: var(--amplada-ocupacio, 0%);
height: 100%;
background: var(--color-marca);
transition: width 0.3s ease; /* la transició SÍ és possible: és CSS de debò */
}El JavaScript només aporta la dada; tota la presentació —inclosa la transició, impossible en un estil en línia— continua al CSS. És el patró recomanat per a estils dinàmics.
- Comparativa final i decisió del curs
| Criteri | CSS global | Estils en línia | CSS Modules | CSS-in-JS | Utilitats (Tailwind) |
|---|---|---|---|---|---|
| Àmbit | Global | Només aquell element | Local automàtic | Local automàtic | Global atòmic |
| Risc de col·lisió | Alt | Nul | Nul | Nul | Nul |
Pseudoclasses i :hover |
Sí | No | Sí | Sí | Sí |
| Media queries | Sí | No | Sí | Sí | Sí |
| Animacions | Sí | No | Sí | Sí | Sí |
| Dinamisme amb dades | Només per classes | Total | Classes + variables CSS | Total | Classes condicionals |
| Eines necessàries | Cap | Cap | Cap amb Vite | Una llibreria | Una eina de compilació |
| Cost en execució | Nul | Baix | Nul | Mitjà | Nul |
| L'estil viatja amb el component | No | Sí | Sí | Sí | Sí |
| Corba d'aprenentatge | Nul·la | Baixa | Baixa | Mitjana | Mitjana |
| Quan usar-lo | Reinicis, tipografia, variables | Valors calculats en execució | Estil de components: per defecte | Sistemes de disseny amb molt dinamisme | Equips que volen consistència i velocitat |
La combinació que adopta CicloUrbano
No és «triar-ne una i descartar la resta»: les estratègies es complementen, i aquest repartiment és el que veuràs a la majoria de projectes amb Vite:
flowchart TD
A["src/index.css<br/>GLOBAL"] --> A1["Reinici, tipografia,<br/>variables de la paleta"]
B["Component.module.css<br/>CSS MODULES"] --> B1["Tot l'estil del component:<br/>layout, colors, hover, medias"]
C["style={{ }}<br/>EN LÍNIA"] --> C1["Només valors calculats,<br/>preferiblement com a variable CSS"]
Estructura de fitxers resultant:
src/ ├── components/ │ ├── TargetaBicicleta.jsx │ ├── TargetaBicicleta.module.css <- l'estil viu al costat del component │ ├── EtiquetaEstat.jsx │ ├── EtiquetaEstat.module.css │ └── … ├── estils/ │ └── comuns.module.css <- regles compartides entre mòduls ├── utilitats/ │ └── classes.js <- el helper de classes condicionals ├── dades/ │ └── domini.js ├── App.jsx ├── index.css <- reinici + variables globals └── main.jsx
Amb això, TargetaBicicleta és per fi una unitat completa: el seu marcatge, la seva lògica i el seu estil són a la mateixa carpeta, amb el mateix nom, i es poden esborrar junts sense deixar rastre. Aquesta era la promesa de la primera lliçó del mòdul.
Errors Habituals i Consells
- Usar
classen lloc declassName. L'error més repetit de React.classés paraula reservada de JavaScript. - Oblidar el
.moduleal nom del fitxer.TargetaBicicleta.cssés global; nomésTargetaBicicleta.module.cssactiva l'àmbit local. El símptoma és que l'import estilsarriba buit i les classes no s'apliquen. - Escriure
className={estils}en lloc declassName={estils.targeta}.estilsés un objecte; React acabarà pintant[object Object]com a classe. - Referir-se a una classe que no existeix al mòdul.
estils.targeatvalundefinedi l'element es queda sense classe, sense cap error. És el fallo més difícil de veure: revisa sempre l'HTML generat a l'inspector. - Escriure CSS amb guions en un mòdul i accedir-hi amb punt.
.targeta-destacadaobliga aestils['targeta-destacada']. Usa camelCase als mòduls. - Inserir
falseen una plantilla de classe.`targeta ${destacada && 'destacada'}`produeix literalment"targeta false"quan la condició és falsa. Usa el helperclasseso un ternari amb cadena buida. - Posar unitats on React ja les posa, o no posar-les on calen. A
style,width: 300és300px, peròwidth: '80%'necessita ser cadena. Iflex: 1ozIndex: 10van sense unitat. - Intentar
:hoveramb estils en línia. No existeix. Si el necessites, cal una classe. - Consell: comença per les variables CSS. Abans d'escriure el primer component d'un projecte, defineix la paleta a
:root. Estalvia una refactorització garantida. - Consell: anomena les classes d'un mòdul pel seu paper, no pel seu aspecte.
.titoli.preuenvelleixen bé;.textVerdno. - Consell: si un component necessita més de tres o quatre classes condicionals, revisa el seu disseny. Sol ser senyal que calen dos components diferents.
- Nota sobre accessibilitat: el color mai no ha de ser l'únic portador d'informació. El nostre
EtiquetaEstatencerta en mostrar també el text de l'estat junt amb el color. Això es tracta a fons a Accessibilitat en Components Interactius.
Exercicis
Exercici 1
Migra EtiquetaEstat de CSS global a CSS Modules. Crea src/components/EtiquetaEstat.module.css amb una classe base i una classe per cada estat (disponible, alquilada, mantenimiento), fent servir les variables CSS de la paleta. Adapta el component perquè combini la classe base amb la de l'estat rebut per props, i afegeix un efecte de :hover que augmenti lleugerament l'opacitat.
Pista: la classe de l'estat cal obtenir-la de l'objecte d'estils, no construir-la amb una plantilla de cadena.
Exercici 2
Crea el component DisponibilitatEstacio complet (src/components/DisponibilitatEstacio.jsx i el seu mòdul CSS). Ha de rebre una estacio del domini i un nombre de places ocupades, i mostrar el nom, el recompte i una barra de progrés. L'amplada de la barra s'ha de passar com a variable CSS des del component, i el color ha de canviar segons l'ocupació: verd per sota del 60 %, taronja entre el 60 % i el 85 %, vermell per sobre.
Usa el helper classes per triar la classe de color.
Exercici 3
Per a cada situació, indica quina estratègia d'estil triaries i per què. Justifica-ho en dues o tres frases.
- El reinici de marges i el
box-sizingde tota l'aplicació. - El color de fons d'una fila de taula que depèn de l'estat de la bicicleta (tres valors possibles).
- La posició exacta d'un tooltip, calculada a partir de les coordenades del ratolí.
- Un botó de marca reutilitzable amb estats
:hover,:focusi:disabled. - La paleta de colors corporativa de CicloUrbano.
Solucions
Solució 1.
/* src/components/EtiquetaEstat.module.css */
.etiqueta {
display: inline-block;
padding: 0.15rem 0.6rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
vertical-align: middle;
color: var(--color-superficie);
opacity: 0.92;
transition: opacity 0.2s ease;
}
.etiqueta:hover {
opacity: 1;
}
.disponible { background-color: var(--color-marca); }
.alquilada { background-color: var(--color-llogada); }
.mantenimiento { background-color: var(--color-manteniment); }// src/components/EtiquetaEstat.jsx
import { classes } from '../utilitats/classes.js';
import estils from './EtiquetaEstat.module.css';
/**
* Distintiu visual de l'estat d'una bicicleta.
* Props:
* - estat (cadena, opcional, per defecte 'disponible'):
* 'disponible' | 'alquilada' | 'mantenimiento'
*/
function EtiquetaEstat({ estat = 'disponible' }) {
return (
<span className={classes(estils.etiqueta, estils[estat])}>{estat}</span>
);
}
export default EtiquetaEstat;El punt clau: className={`estat estat--${estat}`} deixa de funcionar amb mòduls, perquè les classes estan renombrades. Cal consultar-les a l'objecte amb estils[estat]. I si arribés un estat inesperat, estils[estat] seria undefined i el helper classes el descartaria sense trencar res: l'etiqueta es pintaria sense color de fons en lloc de fallar.
Solució 2.
/* src/components/DisponibilitatEstacio.module.css */
.contenidor {
background: var(--color-superficie);
border: 1px solid var(--color-vora);
border-radius: var(--radi);
padding: var(--espai) 1.25rem;
margin-bottom: var(--espai);
max-width: 32rem;
}
.text {
margin: 0 0 0.5rem;
font-size: 0.95rem;
}
.barra {
height: 10px;
background: var(--color-fons);
border: 1px solid var(--color-vora);
border-radius: 999px;
overflow: hidden;
}
.ompliment {
/* L'amplada arriba com a variable CSS des del component */
width: var(--ocupacio, 0%);
height: 100%;
transition: width 0.3s ease;
}
.baixa { background: var(--color-marca); }
.mitjana { background: var(--color-llogada); }
.alta { background: var(--color-manteniment); }// src/components/DisponibilitatEstacio.jsx
import { classes } from '../utilitats/classes.js';
import estils from './DisponibilitatEstacio.module.css';
/**
* Ocupació d'una estació de CicloUrbano.
* Props:
* - estacio (objecte, obligatori) { id, nom, barri, places }
* - ocupades (número, opcional, per defecte 0)
*/
function DisponibilitatEstacio({ estacio, ocupades = 0 }) {
const percentatge = Math.round((ocupades / estacio.places) * 100);
const nivell = percentatge < 60 ? 'baixa' : percentatge <= 85 ? 'mitjana' : 'alta';
return (
<div className={estils.contenidor}>
<p className={estils.text}>
{estacio.nom} ({estacio.barri}): {ocupades} de {estacio.places} places
ocupades · {percentatge} %
</p>
<div className={estils.barra}>
<div
className={classes(estils.ompliment, estils[nivell])}
style={{ '--ocupacio': `${percentatge}%` }}
/>
</div>
</div>
);
}
export default DisponibilitatEstacio;// src/App.jsx (fragment)
<DisponibilitatEstacio estacio={estacions[0]} ocupades={11} />
<DisponibilitatEstacio estacio={estacions[1]} ocupades={13} />
<DisponibilitatEstacio estacio={estacions[2]} ocupades={9} />Repartiment de responsabilitats: el component calcula només la dada (percentatge i nivell), el mòdul CSS aporta tota la presentació —inclosa la transició— i l'estil en línia es limita a entregar una variable. Tant percentatge com nivell són valors derivats: cap dels dos és estat, com vas estudiar a la lliçó anterior.
Solució 3.
| Cas | Estratègia | Justificació |
|---|---|---|
1. Reinici i box-sizing |
CSS global | És global per naturalesa: afecta tots els elements del document. Encapsular-ho per component no tindria sentit |
| 2. Fons de fila segons l'estat | CSS Modules amb classe condicional | Són tres valors coneguts d'antuvi: tres classes i un estils[estat]. No cal dinamisme en execució |
| 3. Posició d'un tooltip | Estil en línia (o variable CSS) | Les coordenades només es coneixen en execució i són un continu, no un conjunt de casos. És el cas legítim de l'estil en línia |
4. Botó de marca amb :hover, :focus i :disabled |
CSS Modules | Les pseudoclasses són impossibles en línia, i l'àmbit local evita que un .boto d'un altre component el trepitgi |
| 5. Paleta corporativa | Variables CSS a :root (global) |
Font de veritat única, accessible des de qualsevol mòdul i modificable en execució per a temes alternatius |
Conclusió
Ja saps donar estil a un component de React amb criteri, no per costum. Has vist les quatre estratègies sobre el mateix TargetaBicicleta: el CSS global, còmode però amb un espai de noms compartit on les col·lisions són qüestió de temps; els estils en línia, imbatibles per a valors calculats en execució i inútils per a tota la resta —sense :hover, sense media queries, sense animacions—; els CSS Modules, que Vite suporta de sèrie i que resolen l'àmbit local per construcció; i les alternatives de l'ecosistema, CSS-in-JS i frameworks d'utilitats, amb els seus compromisos ben identificats.
CicloUrbano adopta la combinació que ofereix el millor equilibri: variables CSS a :root per a la paleta i les formes, un .module.css al costat de cada component per a tot el seu estil, i estils en línia només per entregar valors calculats, preferiblement en forma de variable CSS perquè la presentació continuï vivint al CSS. Pel camí has incorporat dues eines que faràs servir constantment: construir classes a partir de les dades amb estils[bicicleta.tipus] i compondre classes condicionals amb el helper classes, que descarta els valors falsos sense embrutar el JSX.
Amb això tanques el Mòdul 2. Saps què és un component com a unitat de disseny i com traçar-ne els límits; saps llegir el codi heretat escrit amb classes i per què tot el nou s'escriu amb funcions; saps parametritzar un component amb props de només lectura i compondre contingut amb children; saps donar-li memòria amb estat, actualitzar-lo de forma immutable i distingir el que es guarda del que es deriva; i ara saps vestir-lo sense que l'estil es descontroli. TargetaBicicleta és, per fi, una unitat completa: marcatge, lògica i estil a la mateixa carpeta.
Però el catàleg continua sent un aparador. El SelectorTipus guarda el tipus triat i no filtra res; ComptadorPlaces resta places que no li importen a ningú més; no hi ha formularis, no hi ha validació i les llistes s'escriuen targeta a targeta. Falta la interacció de debò. Al Mòdul 3: Treballant amb Esdeveniments aprendràs a gestionar els esdeveniments de React amb totes les seves regles, a mostrar o amagar parts de la interfície amb renderitzat condicional, a pintar col·leccions completes amb map i a entendre per fi per a què serveixen les claus que vas veure en la reconciliació, a construir formularis controlats i a validar-los, i a fer que tot això sigui accessible. La lliçó següent és Gestió d'Esdeveniments a React, i a partir d'aquí CicloUrbano deixarà de mirar-se i començarà a respondre.
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
