Styled-Components: Biblioteca CSS-in-JS para a Web Moderna

Publicados: 2022-03-11

CSS foi projetado para documentos, o que a “velha web” deveria conter. O surgimento de pré-processadores como Sass ou Less mostra que a comunidade precisa de mais do que o CSS oferece. Com os aplicativos da web ficando cada vez mais complexos ao longo do tempo, as limitações do CSS tornaram-se cada vez mais visíveis e difíceis de mitigar.

Os componentes com estilo aproveitam o poder de uma linguagem de programação completa — JavaScript — e seus recursos de escopo para ajudar a estruturar o código em componentes. Isso ajuda a evitar as armadilhas comuns de escrever e manter CSS para grandes projetos. Um desenvolvedor pode descrever o estilo de um componente sem risco de efeitos colaterais.

Qual é o problema?

Uma vantagem de usar CSS é que o estilo é completamente desacoplado do código. Isso significa que desenvolvedores e designers podem trabalhar em paralelo sem interferir um no outro.

Por outro lado, styled-components torna mais fácil cair na armadilha do forte acoplamento de estilo e lógica. Max Stoiber explica como evitar isso. Embora a ideia de separar lógica e apresentação definitivamente não seja nova, pode-se ficar tentado a usar atalhos ao desenvolver componentes React. Por exemplo, é fácil criar um componente para um botão de validação que manipula a ação de clique, bem como o estilo do botão. É preciso um pouco mais de esforço para dividi-lo em dois componentes.

O Container/Arquitetura Apresentacional

Este é um princípio bastante simples. Os componentes definem a aparência das coisas ou gerenciam dados e lógica. Um aspecto muito importante dos componentes de apresentação é que eles nunca devem ter dependências. Eles recebem adereços e renderizam DOM (ou filhos) de acordo. Os contêineres, por outro lado, conhecem a arquitetura dos dados (estado, redux, fluxo, etc.), mas nunca devem ser responsáveis ​​pela exibição. O artigo de Dan Abramov é uma explicação muito boa e detalhada dessa arquitetura.

Lembrando SMACSS

Embora o Scalable and Modular Architecture for CSS seja um guia de estilo para organizar o CSS, o conceito básico é aquele que é seguido, na maior parte automaticamente, por styled-components. A ideia é separar o CSS em cinco categorias:

  • Base contém todas as regras gerais.
  • O objetivo do Layout é definir as propriedades estruturais, bem como várias seções de conteúdo (cabeçalho, rodapé, barra lateral, conteúdo, por exemplo).
  • O módulo contém subcategorias para os vários blocos lógicos da interface do usuário.
  • State define classes modificadoras para indicar os estados dos elementos, por exemplo, campo com erro, botão desabilitado.
  • O tema contém cor, fonte e outros aspectos cosméticos que podem ser modificáveis ​​ou dependentes da preferência do usuário.

Manter essa separação ao usar componentes com estilo é fácil. Os projetos geralmente incluem algum tipo de normalização ou redefinição de CSS. Isso normalmente se enquadra na categoria Base . Você também pode definir o tamanho geral da fonte, tamanho da linha, etc. Isso pode ser feito através de CSS normal (ou Sass/Less), ou através da função injectGlobal fornecida por styled-components .

Para regras de layout , se você usar uma estrutura de interface do usuário, provavelmente definirá classes de contêiner ou um sistema de grade. Você pode facilmente usar essas classes em conjunto com suas próprias regras nos componentes de layout que você escreve.

Module é automaticamente seguido pela arquitetura de styled-components , pois os estilos são anexados diretamente aos componentes, em vez de descritos em arquivos externos. Basicamente, cada componente estilizado que você escrever será seu próprio módulo. Você pode escrever seu código de estilo sem se preocupar com efeitos colaterais.

State serão as regras que você define dentro de seus componentes como regras de variáveis. Você simplesmente define uma função para interpolar valores de seus atributos CSS. Se estiver usando uma estrutura de interface do usuário, você também poderá ter classes úteis para adicionar aos seus componentes. Você provavelmente também terá regras de pseudosseletor CSS (hover, foco, etc.)

O tema pode simplesmente ser interpolado dentro de seus componentes. É uma boa ideia definir seu tema como um conjunto de variáveis ​​a serem usadas em toda a sua aplicação. Você pode até mesmo derivar cores programaticamente (usando uma biblioteca ou manualmente), por exemplo, para lidar com contrastes e realces. Lembre-se de que você tem todo o poder de uma linguagem de programação à sua disposição!

Junte-os para uma solução

É importante mantê-los juntos, para facilitar a navegação; Não queremos organizá-los por tipo (apresentação vs. lógica), mas sim por funcionalidade.

Assim, teremos uma pasta para todos os componentes genéricos (botões e afins). Os demais devem ser organizados de acordo com o projeto e suas funcionalidades. Por exemplo, se tivermos recursos de gerenciamento de usuários, devemos agrupar todos os componentes específicos desse recurso.

Para aplicar a arquitetura de contêiner/apresentação de componentes com estilo a uma abordagem SMACSS, precisamos de um tipo extra de componente: estrutural. Acabamos com três tipos de componentes; estilo, estrutural e contêiner. Como styled-components decora uma tag (ou componente), precisamos desse terceiro tipo de componente para estruturar o DOM. Em alguns casos, pode ser possível permitir que um componente de contêiner manipule a estrutura de subcomponentes, mas quando a estrutura DOM se torna complexa e é necessária para fins visuais, é melhor separá-los. Um bom exemplo é uma tabela, onde o DOM normalmente fica bastante detalhado.

Projeto de exemplo

Vamos construir um pequeno aplicativo que exiba receitas para ilustrar esses princípios. Podemos começar a construir um componente Receitas. O componente pai será um controlador. Ele irá lidar com o estado—neste caso, a lista de receitas. Ele também chamará uma função de API para buscar os dados.

 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} /> ) } }

Ele irá renderizar a lista de receitas, mas não precisa (e não deveria) saber como. Então renderizamos outro componente que obtém a lista de receitas e gera o DOM:

 class RecipesContainer extends Component{ render() { let {recipes} = this.props return ( <TilesContainer> {recipes.map(recipe => (<Recipe key={recipe.id} {...recipe}/>))} </TilesContainer> ) } }

Aqui, na verdade, queremos fazer uma grade de ladrilhos. Pode ser uma boa ideia tornar o layout de ladrilho real um componente genérico. Então, se extrairmos isso, obteremos um novo componente que se parece com isso:

 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; } `

Observe que esse componente é puramente de apresentação. Ele define seu estilo e envolve quaisquer filhos que receber dentro de outro elemento DOM com estilo que define a aparência dos blocos. É um bom exemplo de como seus componentes genéricos de apresentação ficarão arquitetonicamente.

Então, precisamos definir como é uma receita. Precisamos de um componente de contêiner para descrever o DOM relativamente complexo, bem como definir o estilo quando necessário. Terminamos com isso:

 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 aqui que o contêiner faz alguma geração de DOM, mas é a única lógica que ele contém. Lembre-se de que você pode definir estilos aninhados, portanto, não é necessário criar um elemento com estilo para cada tag que requer estilo. É o que fazemos aqui para o nome e a quantidade do item ingrediente. Claro, poderíamos dividi-lo ainda mais e criar um novo componente para um ingrediente. Depende de você – dependendo da complexidade do projeto – determinar a granularidade. Nesse caso, é apenas um componente com estilo definido junto com o restante no arquivo 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 o propósito deste exercício, usei o ThemeProvider. Ele injeta o tema nos adereços dos componentes estilizados. Você pode simplesmente usá-lo como color: ${props => props.theme.core_color} , estou apenas usando um pequeno wrapper para proteger de atributos ausentes no tema:

 const theme = (key) => (prop) => _.get(prop.theme, key) || console.warn('missing key', key)

Você também pode definir suas próprias constantes em um módulo e usá-las. Por exemplo: color: ${styleConstants.core_color}

Prós

Uma vantagem de usar componentes com estilo é que você pode usá-los tão pouco quanto quiser. Você pode usar sua estrutura de interface do usuário favorita e adicionar componentes de estilo em cima dela. Isso também significa que você pode migrar facilmente um componente de projeto existente por componente. Você pode optar por estilizar a maior parte do layout com CSS padrão e usar apenas componentes com estilo para componentes reutilizáveis.

Contras

Designers/integradores de estilo precisarão aprender JavaScript muito básico para lidar com variáveis ​​e usá-las no lugar de Sass/Less.

Eles também terão que aprender a navegar na estrutura do projeto, embora eu argumente que encontrar os estilos de um componente na pasta desse componente é mais fácil do que ter que encontrar o arquivo CSS/Sass/Less correto que contém a regra que você precisa modificar.

Eles também precisarão mudar um pouco suas ferramentas se quiserem realce de sintaxe, linting, etc. Um bom lugar para começar é com este plugin Atom e este plugin babel.