Cómo usar componentes con estilo en React

Publicado: 2022-03-10
Resumen rápido ↬ Si bien el enfoque basado en componentes ha marcado el comienzo de una nueva frontera en la forma en que creamos aplicaciones web, no deja de tener imperfecciones, una de las cuales es su usabilidad y escalabilidad con CSS. Esto ha dado lugar a una nueva forma de construir y administrar nuestros estilos de una manera específica para cada componente , también conocida como CSS-in-JS.

Los componentes con estilo son una herramienta CSS-in-JS que cierra la brecha entre los componentes y el estilo, y ofrece numerosas funciones para que pueda comenzar a diseñar componentes de una manera funcional y reutilizable. En este artículo, aprenderá los conceptos básicos de los componentes con estilo y cómo aplicarlos correctamente a sus aplicaciones React. Debería haber trabajado en React anteriormente antes de seguir este tutorial. Si está buscando varias opciones para diseñar los componentes de React, puede consultar nuestra publicación anterior sobre el tema.

El núcleo de CSS es la capacidad de apuntar a cualquier elemento HTML, globalmente, sin importar su posición en el árbol DOM. Esto puede ser un obstáculo cuando se usa con componentes, porque los componentes exigen, en una medida razonable, colocación (es decir, mantener activos como estados y estilo) más cerca de donde se usan (lo que se conoce como localización).

En las propias palabras de React, los componentes con estilo son " primitivas visuales para componentes ", y su objetivo es brindarnos una forma flexible de diseñar componentes. El resultado es un estrecho acoplamiento entre los componentes y sus estilos.

Nota: Los componentes con estilo están disponibles tanto para React como para React Native, y aunque definitivamente debería consultar la guía de React Native, nuestro enfoque aquí estará en los componentes con estilo para React.

¡Más después del salto! Continúe leyendo a continuación ↓

¿Por qué componentes con estilo?

Además de ayudarlo a definir los estilos, los componentes con estilo incluyen las siguientes características:

  • Prefijo automático de proveedores
    Puede usar propiedades CSS estándar y los componentes con estilo agregarán prefijos de proveedores en caso de que sean necesarios.
  • Nombres de clase únicos
    Los componentes con estilo son independientes entre sí y no tiene que preocuparse por sus nombres porque la biblioteca se encarga de eso por usted.
  • Eliminación de estilos muertos
    Los componentes con estilo eliminan los estilos no utilizados, incluso si están declarados en su código.
  • y muchos más.

Instalación

La instalación de componentes con estilo es fácil. Puedes hacerlo a través de un CDN o con un gestor de paquetes como Yarn…

 yarn add styled-components

… o npm:

 npm i styled-components

Nuestra demostración usa create-react-app.

Empezando

Quizás lo primero que notará acerca de los componentes con estilo es su sintaxis, que puede ser abrumadora si no comprende la magia detrás de los componentes con estilo. En pocas palabras, los componentes con estilo utilizan los literales de plantilla de JavaScript para cerrar la brecha entre los componentes y los estilos. Entonces, cuando crea un componente con estilo, lo que realmente está creando es un componente React con estilos. Se parece a esto:

 import styled from "styled-components"; // Styled component named StyledButton const StyledButton = styled.button` background-color: black; font-size: 32px; color: white; `; function Component() { // Use it like any other component. return <StyledButton> Login </StyledButton>; }

Aquí, StyledButton es el componente con estilo y se representará como un botón HTML con los estilos contenidos. styled es un método de utilidad interno que transforma el estilo de JavaScript en CSS real.

En HTML y CSS sin procesar, tendríamos esto:

 button { background-color: black; font-size: 32px; color: white; } <button> Login </button>

Si los componentes con estilo son componentes de React, ¿podemos usar accesorios? Si podemos.

Adaptación basada en accesorios

Los componentes con estilo son funcionales , por lo que podemos diseñar fácilmente elementos dinámicamente. Supongamos que tenemos dos tipos de botones en nuestra página, uno con fondo negro y el otro azul. No tenemos que crear dos componentes con estilo para ellos; podemos adaptar su estilo en función de sus accesorios.

 import styled from "styled-components"; const StyledButton = styled.button` min-width: 200px; border: none; font-size: 18px; padding: 7px 10px; /* The resulting background color will be based on the bg props. */ background-color: ${props => props.bg === "black" ? "black" : "blue"; `; function Profile() { return ( <div> <StyledButton bg="black">Button A</StyledButton> <StyledButton bg="blue">Button B</StyledButton> </div> ) }

Debido a que StyledButton es un componente de React que acepta accesorios, podemos asignar un color de fondo diferente según la existencia o el valor del accesorio bg .

Sin embargo, notará que no le hemos dado a nuestro botón un type . Vamos a hacer eso:

 function Profile() { return ( <> <StyledButton bg="black" type="button"> Button A </StyledButton> <StyledButton bg="blue" type="submit" onClick={() => alert("clicked")}> Button B </StyledButton> </> ); }

Los componentes con estilo pueden diferenciar entre los tipos de accesorios que reciben. Saben que el type es un atributo HTML, por lo que en realidad procesan <button type="button">Button A</button> , mientras usan la propiedad bg en su propio procesamiento. ¿Observe cómo también adjuntamos un controlador de eventos?

Hablando de atributos, una sintaxis extendida nos permite administrar accesorios usando el constructor attrs . Mira esto:

 const StyledContainer = styled.section.attrs((props) => ({ width: props.width || "100%", hasPadding: props.hasPadding || false, }))` --container-padding: 20px; width: ${(props) => props.width}; // Falls back to 100% padding: ${(props) => (props.hasPadding && "var(--container-padding)") || "none"}; `;

¿Te das cuenta de que no necesitamos un ternario al establecer el ancho? Eso es porque ya hemos establecido un valor predeterminado para él con width: props.width || "100%", width: props.width || "100%", . Además, ¡usamos propiedades personalizadas de CSS porque podemos!

Nota: si los componentes con estilo son componentes de React y podemos pasar accesorios, ¿podemos usar estados también? La cuenta de GitHub de la biblioteca tiene un problema al abordar este mismo asunto.

Ampliación de estilos

Supongamos que está trabajando en una página de destino y ha configurado su contenedor en un ancho máximo determinado para mantener las cosas centradas. Tienes un StyledContainer para eso:

 const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `;

Luego, descubre que necesita un contenedor más pequeño, con un relleno de 10 píxeles en ambos lados, en lugar de 20 píxeles. Su primer pensamiento podría ser crear otro componente con estilo, y tendría razón, pero no tardaría mucho en darse cuenta de que está duplicando estilos.

 const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `; const StyledSmallContainer = styled.section` max-width: 1024px; padding: 0 10px; margin: 0 auto; `;

Antes de continuar y crear StyledSmallContainer , como en el fragmento anterior, aprendamos la forma de reutilizar y heredar estilos. Es más o menos como funciona el operador de spread :

 const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `; // Inherit StyledContainer in StyledSmallConatiner const StyledSmallContainer = styled(StyledContainer)` padding: 0 10px; `; function Home() { return ( <StyledContainer> <h1>The secret is to be happy</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer> <h1>The road goes on and on</h1> </StyledSmallContainer> ); }

En su StyledSmallContainer , obtendrá todos los estilos de StyledContainer , pero el relleno se anulará. Tenga en cuenta que, por lo general, obtendrá un elemento de sección representado para StyledSmallContainer , porque eso es lo que representa StyledContainer . Pero eso no significa que esté tallado en piedra o sea inmutable.

El accesorio polimórfico “as”

Con el accesorio polimórfico as , puede intercambiar el elemento final que se representa. Un caso de uso es cuando heredas estilos (como en el último ejemplo). Si, por ejemplo, prefiere un div a una section para StyledSmallContainer , puede pasar el as prop a su componente con estilo con el valor de su elemento preferido, así:

 function Home() { return ( <StyledContainer> <h1>It's business, not personal</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer as="div"> <h1>Never dribble when you can pass</h1> </StyledSmallContainer> ); }

Ahora, StyledSmallContainer se representará como un div . Incluso podría tener un componente personalizado como su valor:

 function Home() { return ( <StyledContainer> <h1>It's business, not personal</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer as={StyledContainer}> <h1>Never dribble when you can pass</h1> </StyledSmallContainer> ); }

No lo des por sentado.

Sintaxis similar a SCSS

El preprocesador CSS Stylis permite que los componentes con estilo admitan sintaxis similar a SCSS, como el anidamiento:

 const StyledProfileCard = styled.div` border: 1px solid black; > .username { font-size: 20px; color: black; transition: 0.2s; &:hover { color: red; } + .dob { color: grey; } } `; function ProfileCard() { return ( <StyledProfileCard> <h1 className="username">John Doe</h1> <p className="dob"> Date: <span>12th October, 2013</span> </p> <p className="gender">Male</p> </StyledProfileCard> ); }

Animación

Los componentes con estilo tienen un asistente de keyframes que ayuda a construir fotogramas clave de animación (reutilizables). La ventaja aquí es que los fotogramas clave se separarán de los componentes con estilo y se pueden exportar y reutilizar donde sea necesario.

 import styled, {keyframes} from "styled-components"; const slideIn = keyframes` from { opacity: 0; } to { opacity: 1; } `; const Toast = styled.div` animation: ${slideIn} 0.5s cubic-bezier(0.4, 0, 0.2, 1) both; border-radius: 5px; padding: 20px; position: fixed; `;

Estilo global

Si bien el objetivo original de CSS-in-JS y, por extensión, los componentes con estilo es definir el alcance de los estilos, también podemos aprovechar el estilo global de los componentes con estilo. Debido a que estamos trabajando principalmente con estilos de alcance, puede pensar que es una configuración de fábrica invariable, pero estaría equivocado. Piénselo: ¿Qué es realmente el alcance? Es técnicamente posible para nosotros, en nombre del estilo global, hacer algo similar a esto:

 ReactDOM.render( <StyledApp> <App /> </StyledApp>, document.getElementById("root") );

Pero ya tenemos una función auxiliar, createGlobalStyle , cuya única razón de ser es el estilo global. Entonces, ¿por qué negarle su responsabilidad?

Una cosa para la que podemos usar createGlobalStyle es para normalizar el CSS:

 import {createGlobalStyle} from "styled-components"; const GlobalStyle = createGlobalStyle` /* Your css reset here */ `; // Use your GlobalStyle function App() { return ( <div> <GlobalStyle /> <Routes /> </div> ); }

Nota: los estilos creados con createGlobalStyle no aceptan elementos secundarios. Obtenga más información en la documentación.

En este punto, es posible que se pregunte por qué deberíamos molestarnos en usar createGlobalStlye . Aquí hay algunas razones:

  • No podemos apuntar a nada fuera del procesamiento raíz sin él (por ejemplo, html , body , etc.).
  • createGlobalStyle inyecta estilos pero no representa ningún elemento real. Si observa detenidamente el último ejemplo, notará que no especificamos ningún elemento HTML para representar. Esto es genial porque es posible que en realidad no necesitemos el elemento. Después de todo, nos preocupan los estilos globales. Nos dirigimos a los selectores en general, no a elementos específicos.
  • createGlobalStyle no tiene alcance y se puede representar en cualquier parte de nuestra aplicación y será aplicable siempre que esté en el DOM. Piense en el concepto , no en la estructura .
 import {createGlobalStyle} from "styled-components"; const GlobalStyle = createGlobalStyle` /* Your css reset here */ .app-title { font-size: 40px; } `; const StyledNav = styled.nav` /* Your styles here */ `; function Nav({children}) { return ( <StyledNav> <GlobalStyle /> {children} </StyledNav> ); } function App() { return ( <div> <Nav> <h1 className="app-title">STYLED COMPONENTS</h1> </Nav> <Main /> <Footer /> </div> ); }

Si piensa en la estructura, entonces app-title no debe tener el estilo establecido en GlobalStyle . Pero no funciona de esa manera. Dondequiera que elija renderizar su GlobalStyle , se inyectará cuando se renderice su componente.

Tenga cuidado : createGlobalStyles solo se representará si y cuando esté en el DOM.

Ayudante de CSS

Ya hemos visto cómo adaptar estilos basados ​​en accesorios. ¿Y si quisiéramos ir un poco más allá? La función auxiliar de CSS ayuda a lograr esto. Supongamos que tenemos dos campos de entrada de texto con estados: vacío y activo, cada uno con un color diferente. Podemos hacer esto:

 const StyledTextField = styled.input` color: ${(props) => (props.isEmpty ? "none" : "black")}; `;

Todo esta bien. Posteriormente, si necesitamos agregar otro estado de relleno, tendríamos que modificar nuestros estilos:

 const StyledTextField = styled.input` color: ${(props) => props.isEmpty ? "none" : props.active ? "purple" : "blue"}; `;

Ahora la operación ternaria está creciendo en complejidad. ¿Qué pasa si agregamos otro estado a nuestros campos de entrada de texto más adelante? ¿O qué pasa si queremos darle a cada estado estilos adicionales, además del color? ¿Te imaginas encajar los estilos en la operación ternaria? El ayudante css es útil.

 const StyledTextField = styled.input` width: 100%; height: 40px; ${(props) => (props.empty && css` color: none; backgroundcolor: white; `) || (props.active && css` color: black; backgroundcolor: whitesmoke; `)} `;

Lo que hemos hecho es expandir nuestra sintaxis ternaria para acomodar más estilos y con una sintaxis más comprensible y organizada. Si la declaración anterior parece incorrecta, es porque el código está tratando de hacer demasiado. Entonces, demos un paso atrás y refinemos:

 const StyledTextField = styled.input` width: 100%; height: 40px; // 1. Empty state ${(props) => props.empty && css` color: none; backgroundcolor: white; `} // 2. Active state ${(props) => props.active && css` color: black; backgroundcolor: whitesmoke; `} // 3. Filled state ${(props) => props.filled && css` color: black; backgroundcolor: white; border: 1px solid green; `} `;

Nuestro refinamiento divide el estilo en tres partes manejables y fáciles de entender. es una victoria

StyleSheetManager

Al igual que el asistente de CSS, StyleSheetManager es un método auxiliar para modificar cómo se procesan los estilos. Se necesitan ciertos accesorios, como disableVendorPrefixes (puede consultar la lista completa), que lo ayudan a optar por los prefijos de proveedores de su subárbol.

 import styled, {StyleSheetManager} from "styled-components"; const StyledCard = styled.div` width: 200px; backgroundcolor: white; `; const StyledNav = styled.div` width: calc(100% - var(--side-nav-width)); `; function Profile() { return ( <div> <StyledNav /> <StyleSheetManager disableVendorPrefixes> <StyledCard> This is a card </StyledCard> </StyleSheetManager> </div> ); }

disableVendorPrefixes se pasa como accesorio a <StyleSheetManager> . Por lo tanto, los componentes con estilo envueltos por <StyleSheetManager> estarían deshabilitados, pero no los de <StyledNav> .

Depuración más fácil

Cuando le presenté componentes con estilo a uno de mis colegas, una de sus quejas fue que es difícil ubicar un elemento renderizado en el DOM, o en React Developer Tools, para el caso. Este es uno de los inconvenientes de los componentes con estilo: al tratar de proporcionar nombres de clase únicos, asigna hashes únicos a los elementos, que resultan ser crípticos, pero hace que el displayName sea legible para facilitar la depuración.

 import React from "react"; import styled from "styled-components"; import "./App.css"; const LoginButton = styled.button` background-color: white; color: black; border: 1px solid red; `; function App() { return ( <div className="App"> <LoginButton>Login</LoginButton> </div> ); }

De forma predeterminada, los componentes con estilo LoginButton como <button class="LoginButton-xxxx xxxx">Login</button> en el DOM y como LoginButton en React Developer Tools, lo que facilita la depuración. Podemos alternar el booleano displayName si no queremos este comportamiento. Esto requiere una configuración de Babel.

Nota : en la documentación, se especifica el paquete babel-plugin-styled-components , así como un archivo de configuración .babelrc . El problema con esto es que, debido a que estamos usando create-react-app , no podemos configurar muchas cosas a menos que expulsemos. Aquí es donde entran las macros de Babel.

Necesitaremos instalar babel-plugin-macros con npm o Yarn, y luego crear un babel-plugin-macros.config.js en la raíz de nuestra aplicación, con el contenido:

 module.exports = { styledComponents: { displayName: true, fileName: false, }, };

Con el valor de fileName invertido, displayName tendrá el prefijo del nombre de archivo para una precisión aún más única.

Ahora también necesitamos importar desde la macro :

 // Before import styled from "styled-components"; // After import styled from "styled-components/macro";

Conclusión

Ahora que puede componer programáticamente su CSS, no abuse de la libertad. Por lo que vale la pena, haga todo lo posible para mantener la cordura en sus componentes con estilo. No intente componer condicionales pesados, ni suponga que todo debe ser un componente con estilo. Además, no haga demasiadas abstracciones mediante la creación de componentes de estilo incipientes para casos de uso que solo supone que están a la vuelta de la esquina.

Más recursos

  1. Documentación, componentes con estilo
  2. "Construyendo un sistema de componentes reutilizables con React.js y componentes con estilo", Lukas Gisder-Dube
  3. Uso con Next.js
  4. Uso con Gatsby