Styled-Components: libreria CSS-in-JS per il Web moderno
Pubblicato: 2022-03-11I CSS sono stati progettati per i documenti, ciò che ci si aspettava contenesse il "vecchio web". L'emergere di preprocessori come Sass o Less mostra che la comunità ha bisogno di più di ciò che offre CSS. Con le app Web che diventano sempre più complesse nel tempo, i limiti dei CSS sono diventati sempre più visibili e difficili da mitigare.
Styled-components sfrutta la potenza di un linguaggio di programmazione completo, JavaScript, e le sue capacità di ambito per aiutare a strutturare il codice in componenti. Questo aiuta a evitare le insidie comuni di scrivere e mantenere CSS per progetti di grandi dimensioni. Uno sviluppatore può descrivere lo stile di un componente senza rischi di effetti collaterali.
Qual è il problema?
Uno dei vantaggi dell'utilizzo dei CSS è che lo stile è completamente disaccoppiato dal codice. Ciò significa che sviluppatori e designer possono lavorare in parallelo senza interferire tra loro.
D'altra parte, i componenti di stile rendono più facile cadere nella trappola di un forte accoppiamento di stile e logica. Max Stoiber spiega come evitarlo. Sebbene l'idea di separare logica e presentazione non sia sicuramente nuova, si potrebbe essere tentati di prendere scorciatoie quando si sviluppano i componenti React. Ad esempio, è facile creare un componente per un pulsante di convalida che gestisca l'azione del clic e lo stile del pulsante. Ci vuole un po' più di sforzo per dividerlo in due componenti.
Il contenitore/architettura di presentazione
Questo è un principio piuttosto semplice. I componenti definiscono l'aspetto delle cose oppure gestiscono i dati e la logica. Un aspetto molto importante dei componenti di presentazione è che non dovrebbero mai avere alcuna dipendenza. Ricevono oggetti di scena e rendono DOM (o bambini) di conseguenza. I contenitori, d'altra parte, conoscono l'architettura dei dati (stato, redux, flusso, ecc.) Ma non dovrebbero mai essere responsabili della visualizzazione. L'articolo di Dan Abramov è una spiegazione molto buona e dettagliata di questa architettura.
Ricordando SMACSS
Sebbene l'architettura scalabile e modulare per CSS sia una guida di stile per l'organizzazione dei CSS, il concetto di base è seguito, per la maggior parte automaticamente, da componenti di stile. L'idea è di separare i CSS in cinque categorie:
- Base contiene tutte le regole generali.
- Lo scopo di Layout è definire le proprietà strutturali e varie sezioni di contenuto (intestazione, piè di pagina, barra laterale, contenuto, per esempio).
- Il modulo contiene le sottocategorie per i vari blocchi logici dell'interfaccia utente.
- Lo stato definisce le classi di modificatori per indicare gli stati degli elementi, ad esempio campo in errore, pulsante disabilitato.
- Il tema contiene colore, carattere e altri aspetti estetici che possono essere modificabili o dipendenti dalle preferenze dell'utente.
Mantenere questa separazione durante l'utilizzo di componenti stilizzati è facile. I progetti di solito includono una sorta di normalizzazione o ripristino CSS. Questo in genere rientra nella categoria Base . Puoi anche definire il dimensionamento generale del carattere, il dimensionamento della linea, ecc. Questo può essere fatto tramite i normali CSS (o Sass/Less) o tramite la funzione injectGlobal
fornita da styled-components .
Per le regole di layout , se utilizzi un framework dell'interfaccia utente, probabilmente definirà classi contenitore o un sistema a griglia. Puoi facilmente utilizzare quelle classi insieme alle tue regole nei componenti di layout che scrivi.
Il modulo è seguito automaticamente dall'architettura di styled-components , poiché gli stili sono allegati direttamente ai componenti, anziché essere descritti in file esterni. Fondamentalmente, ogni componente in stile che scrivi sarà il suo modulo. Puoi scrivere il tuo codice di stile senza preoccuparti degli effetti collaterali.
Lo stato saranno regole che definisci all'interno dei tuoi componenti come regole variabili. Definisci semplicemente una funzione per interpolare i valori dei tuoi attributi CSS. Se utilizzi un framework dell'interfaccia utente, potresti anche avere classi utili da aggiungere ai tuoi componenti. Probabilmente avrai anche regole di pseudo-selettore CSS (hover, focus, ecc.)
Il tema può essere semplicemente interpolato all'interno dei tuoi componenti. È una buona idea definire il tema come un insieme di variabili da utilizzare nell'applicazione. Puoi anche derivare i colori in modo programmatico (usando una libreria o manualmente), ad esempio per gestire contrasti e luci. Ricorda che hai tutta la potenza di un linguaggio di programmazione a tua disposizione!
Riuniscili per una soluzione
È importante tenerli insieme, per un'esperienza di navigazione più semplice; Non vogliamo organizzarli per tipo (presentazione vs. logica) ma piuttosto per funzionalità.
Pertanto, avremo una cartella per tutti i componenti generici (pulsanti e simili). Gli altri dovrebbero essere organizzati in base al progetto e alle sue funzionalità. Ad esempio, se disponiamo di funzionalità di gestione degli utenti, dovremmo raggruppare tutti i componenti specifici di tale funzionalità.
Per applicare l'architettura di presentazione/contenitore di componenti stilizzati a un approccio SMACSS, abbiamo bisogno di un ulteriore tipo di componente: strutturale. Finiamo con tre tipi di componenti; in stile, strutturale e contenitore. Poiché styled-components decora un tag (o un componente), abbiamo bisogno di questo terzo tipo di componente per strutturare il DOM. In alcuni casi, potrebbe essere possibile consentire a un componente contenitore di gestire la struttura dei sottocomponenti, ma quando la struttura del DOM diventa complessa ed è richiesta per scopi visivi, è meglio separarli. Un buon esempio è una tabella, in cui il DOM in genere diventa piuttosto dettagliato.

Esempio di progetto
Costruiamo una piccola app che mostra le ricette per illustrare questi principi. Possiamo iniziare a costruire un componente Ricette. Il componente padre sarà un controller. Gestirà lo stato, in questo caso l'elenco delle ricette. Chiamerà anche una funzione API per recuperare i dati.
class Recipes extends Component{ constructor (props) { super(props); this.state = { recipes: [] }; } componentDidMount () { this.loadData() } loadData () { getRecipes().then(recipes => { this.setState({recipes}) }) } render() { let {recipes} = this.state return ( <RecipesContainer recipes={recipes} /> ) } }
Renderà l'elenco delle ricette, ma non ha bisogno (e non dovrebbe) di sapere come fare. Quindi eseguiamo il rendering di un altro componente che ottiene l'elenco delle ricette e degli output DOM:
class RecipesContainer extends Component{ render() { let {recipes} = this.props return ( <TilesContainer> {recipes.map(recipe => (<Recipe key={recipe.id} {...recipe}/>))} </TilesContainer> ) } }
Qui, in realtà, vogliamo creare una griglia di tessere. Potrebbe essere una buona idea rendere il layout effettivo delle piastrelle un componente generico. Quindi, se lo estraiamo, otteniamo un nuovo componente simile a questo:
class TilesContainer extends Component { render () { let {children} = this.props return ( <Tiles> { React.Children.map(children, (child, i) => ( <Tile key={i}> {child} </Tile> )) } </Tiles> ) } }
TilesStyles.js:
export const Tiles = styled.div` padding: 20px 10px; display: flex; flex-direction: row; flex-wrap: wrap; ` export const Tile = styled.div` flex: 1 1 auto; ... display: flex; & > div { flex: 1 0 auto; } `
Si noti che questo componente è puramente di presentazione. Definisce il suo stile e avvolge tutti i bambini che riceve all'interno di un altro elemento DOM in stile che definisce l'aspetto delle tessere. È un buon esempio dell'aspetto architettonico dei componenti di presentazione generici.
Quindi, dobbiamo definire come appare una ricetta. Abbiamo bisogno di un componente contenitore per descrivere il DOM relativamente complesso e definire lo stile quando necessario. Finiamo con questo:
class RecipeContainer extends Component { onChangeServings (e) { let {changeServings} = this.props changeServings(e.target.value) } render () { let {title, ingredients, instructions, time, servings} = this.props return ( <Recipe> <Title>{title}</Title> <div>{time}</div> <div>Serving <input type="number" min="1" max="1000" value={servings} onChange={this.onChangeServings.bind(this)}/> </div> <Ingredients> {ingredients.map((ingredient, i) => ( <Ingredient key={i} servings={servings}> <span className="name">{ingredient.name}</span> <span className="quantity">{ingredient.quantity * servings} {ingredient.unit}</span> </Ingredient> ))} </Ingredients> <div> {instructions.map((instruction, i) => (<p key={i}>{instruction}</p>))} </div> </Recipe> ) } }
Nota qui che il contenitore esegue una generazione di DOM, ma è l'unica logica che contiene. Ricorda che puoi definire stili nidificati, quindi non è necessario creare un elemento di stile per ogni tag che richiede uno stile. È quello che facciamo qui per il nome e la quantità dell'ingrediente. Naturalmente, potremmo dividerlo ulteriormente e creare un nuovo componente per un ingrediente. Sta a te, a seconda della complessità del progetto, determinare la granularità. In questo caso, è solo un componente con stile definito insieme al resto nel file RecipeStyles:
export const Recipe = styled.div` background-color: ${theme('colors.background-highlight')}; `; export const Title = styled.div` font-weight: bold; ` export const Ingredients = styled.ul` margin: 5px 0; ` export const Ingredient = styled.li` & .name { ... } & .quantity { ... } `
Ai fini di questo esercizio, ho utilizzato ThemeProvider. Inietta il tema negli oggetti di scena dei componenti in stile. Puoi semplicemente usarlo come color: ${props => props.theme.core_color}
, sto solo usando un piccolo wrapper per proteggere dagli attributi mancanti nel tema:
const theme = (key) => (prop) => _.get(prop.theme, key) || console.warn('missing key', key)
Puoi anche definire le tue costanti in un modulo e usarle invece. Ad esempio: color: ${styleConstants.core_color}
Professionisti
Un vantaggio dell'utilizzo dei componenti in stile è che puoi usarlo quanto meno vuoi. Puoi utilizzare il tuo framework dell'interfaccia utente preferito e aggiungere componenti di stile su di esso. Ciò significa anche che puoi migrare facilmente un componente del progetto esistente per componente. Puoi scegliere di applicare uno stile alla maggior parte del layout con CSS standard e utilizzare solo i componenti con stile per i componenti riutilizzabili.
contro
I progettisti/integratori di stile dovranno imparare JavaScript di base per gestire le variabili e usarle al posto di Sass/Less.
Dovranno anche imparare a navigare nella struttura del progetto, anche se direi che trovare gli stili per un componente nella cartella di quel componente è più facile che dover trovare il giusto file CSS/Sass/Less che contiene la regola che devi modificare.
Avranno anche bisogno di cambiare un po' i loro strumenti se vogliono l'evidenziazione della sintassi, il linting, ecc. Un buon punto di partenza è con questo plugin Atom e questo plugin babel.