Componentes con estilo: Biblioteca CSS-in-JS para la web moderna
Publicado: 2022-03-11CSS fue diseñado para documentos, lo que se esperaba que contuviera la "vieja web". La aparición de preprocesadores como Sass o Less muestra que la comunidad necesita más de lo que ofrece CSS. Con las aplicaciones web cada vez más complejas con el tiempo, las limitaciones de CSS se han vuelto cada vez más visibles y difíciles de mitigar.
Los componentes con estilo aprovechan el poder de un lenguaje de programación completo, JavaScript, y sus capacidades de alcance para ayudar a estructurar el código en componentes. Esto ayuda a evitar los errores comunes de escribir y mantener CSS para proyectos grandes. Un desarrollador puede describir el estilo de un componente sin riesgo de efectos secundarios.
¿Cuál es el problema?
Una ventaja de usar CSS es que el estilo está completamente desacoplado del código. Esto significa que los desarrolladores y diseñadores pueden trabajar en paralelo sin interferir entre sí.
Por otro lado, los componentes con estilo hacen que sea más fácil caer en la trampa de acoplar fuertemente el estilo y la lógica. Max Stoiber explica cómo evitar esto. Si bien la idea de separar la lógica y la presentación definitivamente no es nueva, uno podría verse tentado a tomar atajos al desarrollar componentes de React. Por ejemplo, es fácil crear un componente para un botón de validación que maneje la acción de clic así como el estilo del botón. Se necesita un poco más de esfuerzo para dividirlo en dos componentes.
El Contenedor/Arquitectura Presentacional
Este es un principio bastante simple. Los componentes definen cómo se ven las cosas o administran datos y lógica. Un aspecto muy importante de los componentes de presentación es que nunca deben tener dependencias. Reciben accesorios y representan DOM (o niños) en consecuencia. Los contenedores, por otro lado, conocen la arquitectura de datos (estado, reducción, flujo, etc.) pero nunca deben ser responsables de la visualización. El artículo de Dan Abramov es una muy buena y detallada explicación de esta arquitectura.
Recordando SMACSS
Aunque la Arquitectura escalable y modular para CSS es una guía de estilo para organizar CSS, el concepto básico es seguido, en su mayor parte automáticamente, por componentes con estilo. La idea es separar CSS en cinco categorías:
- Base contiene todas las reglas generales.
- El propósito de Layout es definir las propiedades estructurales, así como varias secciones de contenido (encabezado, pie de página, barra lateral, contenido, por ejemplo).
- El módulo contiene subcategorías para los distintos bloques lógicos de la interfaz de usuario.
- Estado define clases de modificadores para indicar los estados de los elementos, por ejemplo, campo con error, botón deshabilitado.
- El tema contiene color, fuente y otros aspectos estéticos que pueden modificarse o depender de las preferencias del usuario.
Mantener esta separación al usar componentes con estilo es fácil. Los proyectos suelen incluir algún tipo de normalización o reinicio de CSS. Esto generalmente cae en la categoría Base . También puede definir el tamaño de fuente general, el tamaño de línea, etc. Esto se puede hacer a través de CSS normal (o Sass/Less), o mediante la función injectGlobal
proporcionada por componentes con estilo .
Para las reglas de diseño , si usa un marco de interfaz de usuario, probablemente definirá clases de contenedor o un sistema de cuadrícula. Puede usar fácilmente esas clases junto con sus propias reglas en los componentes de diseño que escribe.
Al módulo le sigue automáticamente la arquitectura de los componentes con estilo , ya que los estilos se adjuntan a los componentes directamente, en lugar de estar descritos en archivos externos. Básicamente, cada componente con estilo que escriba será su propio módulo. Puede escribir su código de estilo sin preocuparse por los efectos secundarios.
El estado serán las reglas que defina dentro de sus componentes como reglas variables. Simplemente define una función para interpolar valores de sus atributos CSS. Si usa un marco de interfaz de usuario, es posible que también tenga clases útiles para agregar a sus componentes. Probablemente también tendrá reglas de pseudo-selector de CSS (desplazar, enfocar, etc.)
El tema simplemente se puede interpolar dentro de sus componentes. Es una buena idea definir su tema como un conjunto de variables que se utilizarán en toda su aplicación. Incluso puede derivar colores mediante programación (usando una biblioteca o manualmente), por ejemplo, para manejar contrastes y realces. ¡Recuerda que tienes todo el poder de un lenguaje de programación a tu disposición!
Reunirlos para una solución
Es importante mantenerlos juntos, para una experiencia de navegación más fácil; No queremos organizarlos por tipo (presentación vs lógica) sino por funcionalidad.
Así, tendremos una carpeta para todos los componentes genéricos (botones y demás). Los demás deberán organizarse en función del proyecto y sus funcionalidades. Por ejemplo, si tenemos funciones de administración de usuarios, debemos agrupar todos los componentes específicos de esa función.
Para aplicar la arquitectura de contenedor/presentación de componentes con estilo a un enfoque SMACSS, necesitamos un tipo adicional de componente: estructural. Terminamos con tres tipos de componentes; estilizado, estructural y contenedor. Dado que los componentes con estilo decoran una etiqueta (o componente), necesitamos este tercer tipo de componente para estructurar el DOM. En algunos casos, podría ser posible permitir que un componente contenedor maneje la estructura de los subcomponentes, pero cuando la estructura DOM se vuelve compleja y se requiere con fines visuales, es mejor separarlos. Un buen ejemplo es una tabla, donde el DOM normalmente se vuelve bastante detallado.

Proyecto de ejemplo
Construyamos una pequeña aplicación que muestre recetas para ilustrar estos principios. Podemos comenzar a construir un componente de Recetas. El componente principal será un controlador. Manejará el estado, en este caso, la lista de recetas. También llamará a una función API para obtener los datos.
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} /> ) } }
Representará la lista de recetas, pero no necesita (y no debería) saber cómo hacerlo. Entonces renderizamos otro componente que obtiene la lista de recetas y genera DOM:
class RecipesContainer extends Component{ render() { let {recipes} = this.props return ( <TilesContainer> {recipes.map(recipe => (<Recipe key={recipe.id} {...recipe}/>))} </TilesContainer> ) } }
Aquí, en realidad, queremos hacer una cuadrícula de mosaicos. Puede ser una buena idea hacer que el diseño de mosaico real sea un componente genérico. Entonces, si extraemos eso, obtenemos un nuevo componente que se ve así:
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; } `
Tenga en cuenta que este componente es puramente de presentación. Define su estilo y envuelve cualquier niño que reciba dentro de otro elemento DOM con estilo que define cómo se ven los mosaicos. Es un buen ejemplo de cómo se verán arquitectónicamente sus componentes de presentación genéricos.
Luego, necesitamos definir cómo se ve una receta. Necesitamos un componente contenedor para describir el DOM relativamente complejo y definir el estilo cuando sea necesario. Terminamos con esto:
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> ) } }
Observe aquí que el contenedor genera algo de DOM, pero es la única lógica que contiene. Recuerde que puede definir estilos anidados, por lo que no necesita crear un elemento con estilo para cada etiqueta que requiera estilo. Es lo que hacemos aquí para el nombre y la cantidad del elemento del ingrediente. Por supuesto, podríamos dividirlo aún más y crear un nuevo componente para un ingrediente. Eso depende de usted, dependiendo de la complejidad del proyecto, para determinar la granularidad. En este caso, es solo un componente con estilo definido junto con el resto en el archivo 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 { ... } `
Para el propósito de este ejercicio, he usado ThemeProvider. Inyecta el tema en los accesorios de los componentes con estilo. Simplemente puede usarlo como color: ${props => props.theme.core_color}
, solo estoy usando un pequeño envoltorio para protegerlo de los atributos que faltan en el tema:
const theme = (key) => (prop) => _.get(prop.theme, key) || console.warn('missing key', key)
También puede definir sus propias constantes en un módulo y usarlas en su lugar. Por ejemplo: color: ${styleConstants.core_color}
ventajas
Una ventaja de usar componentes con estilo es que puede usarlos tan poco como desee. Puede usar su marco de interfaz de usuario favorito y agregarle componentes con estilo . Esto también significa que puede migrar fácilmente un proyecto existente componente por componente. Puede optar por diseñar la mayor parte del diseño con CSS estándar y solo usar componentes con estilo para componentes reutilizables.
Contras
Los diseñadores/integradores de estilo necesitarán aprender JavaScript muy básico para manejar variables y usarlas en lugar de Sass/Less.
También tendrán que aprender a navegar por la estructura del proyecto, aunque diría que encontrar los estilos para un componente en la carpeta de ese componente es más fácil que tener que encontrar el archivo CSS/Sass/Less correcto que contiene la regla que necesita modificar.
También necesitarán cambiar un poco sus herramientas si quieren resaltado de sintaxis, pelusa, etc. Un buen lugar para comenzar es con este complemento de Atom y este complemento de babel.