Como usar componentes com estilo no React

Publicados: 2022-03-10
Resumo rápido ↬ Embora a abordagem orientada a componentes tenha introduzido uma nova fronteira na forma como criamos aplicativos da Web, ela também apresenta algumas imperfeições — uma delas é a usabilidade e escalabilidade com CSS. Isso deu origem a uma nova maneira de construir e gerenciar nossos estilos de uma maneira específica de componente , também conhecida como CSS-in-JS.

Componentes com estilo são uma ferramenta CSS-in-JS que preenche a lacuna entre componentes e estilo, oferecendo vários recursos para você começar a usar componentes de estilo de maneira funcional e reutilizável. Neste artigo, você aprenderá o básico dos componentes estilizados e como aplicá-los adequadamente aos seus aplicativos React. Você deveria ter trabalhado no React anteriormente antes de passar por este tutorial. Se você está procurando várias opções de estilização de componentes React, você pode conferir nosso post anterior sobre o assunto.

No centro do CSS está a capacidade de direcionar qualquer elemento HTML – globalmente – independentemente de sua posição na árvore DOM. Isso pode ser um obstáculo quando usado com componentes, porque os componentes exigem, em uma extensão razoável, colocation (ou seja, manter ativos como estados e estilos) mais próximos de onde são usados ​​(conhecido como localização).

Nas palavras do próprio React, componentes com estilo são “ primitivos visuais para componentes ”, e seu objetivo é nos dar uma maneira flexível de estilizar componentes. O resultado é um acoplamento apertado entre os componentes e seus estilos.

Nota: Componentes estilizados estão disponíveis para React e React Native, e embora você deva definitivamente conferir o guia React Native, nosso foco aqui será em componentes estilizados para React.

Mais depois do salto! Continue lendo abaixo ↓

Por que componentes estilizados?

Além de ajudá-lo a definir estilos de escopo, os componentes estilizados incluem os seguintes recursos:

  • Prefixação automática de fornecedores
    Você pode usar propriedades CSS padrão e os componentes estilizados adicionarão prefixos de fornecedor, caso sejam necessários.
  • Nomes de classe exclusivos
    Os componentes com estilo são independentes uns dos outros e você não precisa se preocupar com seus nomes, pois a biblioteca trata disso para você.
  • Eliminação de estilos mortos
    Componentes com estilo removem estilos não utilizados, mesmo que estejam declarados em seu código.
  • e muitos mais.

Instalação

A instalação de componentes com estilo é fácil. Você pode fazer isso através de um CDN ou com um gerenciador de pacotes como o Yarn…

 yarn add styled-components

… ou npm:

 npm i styled-components

Nossa demonstração usa create-react-app.

Começando

Talvez a primeira coisa que você notará sobre os componentes com estilo é sua sintaxe, que pode ser assustadora se você não entender a mágica por trás dos componentes com estilo. Para resumir, os componentes estilizados usam os literais de modelo do JavaScript para preencher a lacuna entre componentes e estilos. Então, quando você cria um componente com estilo, o que você está realmente criando é um componente React com estilos. Se parece com isso:

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

Aqui, StyledButton é o componente com estilo e será renderizado como um botão HTML com os estilos contidos. styled é um método utilitário interno que transforma o estilo do JavaScript em CSS real.

Em HTML e CSS bruto, teríamos isso:

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

Se os componentes com estilo são componentes do React, podemos usar props? Sim, nós podemos.

Adaptando com base em adereços

Componentes estilizados são funcionais , então podemos estilizar facilmente os elementos dinamicamente. Vamos supor que temos dois tipos de botões em nossa página, um com fundo preto e outro azul. Não precisamos criar dois componentes estilizados para eles; podemos adaptar seu estilo com base em seus adereços.

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

Como StyledButton é um componente React que aceita props, podemos atribuir uma cor de fundo diferente com base na existência ou valor da prop bg .

Você notará, no entanto, que não demos ao nosso botão um type . Vamos fazer isso:

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

Componentes estilizados podem diferenciar entre os tipos de adereços que recebem. Eles sabem que type é um atributo HTML, então eles realmente renderizam o <button type="button">Button A</button> , enquanto usam o prop bg em seu próprio processamento. Observe como anexamos um manipulador de eventos também?

Falando em atributos, uma sintaxe estendida nos permite gerenciar props usando o construtor attrs . Veja isso:

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

Observe como não precisamos de um ternário ao definir a largura? Isso porque já definimos um padrão para ele com width: props.width || "100%", width: props.width || "100%", . Além disso, usamos propriedades personalizadas CSS porque podemos!

Nota: Se os componentes com estilo são componentes do React e podemos passar props, também podemos usar os estados? A conta do GitHub da biblioteca tem um problema abordando exatamente esse assunto.

Estendendo Estilos

Digamos que você esteja trabalhando em uma página de destino e tenha definido seu contêiner para uma certa largura máxima para manter as coisas centralizadas. Você tem um StyledContainer para isso:

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

Então, você descobre que precisa de um contêiner menor, com preenchimento de 10 pixels em ambos os lados, em vez de 20 pixels. Seu primeiro pensamento pode ser criar outro componente de estilo, e você estaria certo, mas não levaria muito tempo antes de perceber 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 prosseguir e criar StyledSmallContainer , como no snippet acima, vamos aprender como reutilizar e herdar estilos. É mais ou menos como o operador de spread funciona:

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

Em seu StyledSmallContainer , você obterá todos os estilos de StyledContainer , mas o preenchimento será substituído. Lembre-se de que, normalmente, você obterá um elemento de seção renderizado para StyledSmallContainer , porque é isso que StyledContainer renderiza. Mas isso não significa que seja esculpido em pedra ou imutável.

O suporte polimórfico “como”

Com o prop as polymorphic, você pode trocar o elemento final que é renderizado. Um caso de uso é quando você herda estilos (como no último exemplo). Se, por exemplo, você preferir um div a uma section para StyledSmallContainer , você pode passar o as prop para seu componente com estilo com o valor do seu elemento preferido, assim:

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

Agora, StyledSmallContainer será renderizado como um div . Você pode até ter um componente personalizado como seu 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> ); }

Não tome isso como garantido.

Sintaxe do tipo SCSS

O pré-processador CSS Stylis permite que componentes com estilo suportem sintaxe semelhante a SCSS, como aninhamento:

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

Animação

Os componentes com estilo têm um auxiliar de keyframes -chave que auxilia na construção de quadros-chave de animação (reutilizáveis). A vantagem aqui é que os quadros-chave serão separados dos componentes estilizados e poderão ser exportados e reutilizados sempre que necessário.

 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

Embora o objetivo original do CSS-in-JS e, por extensão, dos componentes estilizados seja o escopo dos estilos, também podemos aproveitar o estilo global dos componentes estilizados. Como estamos trabalhando principalmente com estilos com escopo, você pode pensar que é uma configuração de fábrica invariável, mas está errado. Pense nisso: o que realmente é o escopo? É tecnicamente possível para nós - em nome do estilo global - fazer algo semelhante a isto:

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

Mas já temos uma função auxiliar — createGlobalStyle — cuja única razão de existência é o estilo global. Então, por que negar sua responsabilidade?

Uma coisa para a qual podemos usar createGlobalStyle é normalizar o CSS:

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

Nota: Estilos criados com createGlobalStyle não aceitam filhos. Saiba mais na documentação.

Neste ponto, você deve estar se perguntando por que devemos nos preocupar em usar createGlobalStlye . Aqui estão algumas razões:

  • Não podemos direcionar nada fora da renderização raiz sem ele (por exemplo, html , body , etc.).
  • createGlobalStyle injeta estilos, mas não renderiza nenhum elemento real. Se você observar atentamente o último exemplo, notará que não especificamos nenhum elemento HTML para renderizar. Isso é legal porque talvez não precisemos realmente do elemento. Afinal, estamos preocupados com estilos globais. Estamos direcionando seletores em geral, não elementos específicos.
  • createGlobalStyle não tem escopo e pode ser renderizado em qualquer lugar em nosso aplicativo e será aplicável desde que esteja no DOM. Pense no conceito , não na estrutura .
 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> ); }

Se você pensar na estrutura, app-title não deve ser estilizado conforme definido em GlobalStyle . Mas não funciona assim. Onde quer que você escolha renderizar seu GlobalStyle , ele será injetado quando seu componente for renderizado .

Cuidado : createGlobalStyles só será renderizado se e quando estiver no DOM.

Auxiliar de CSS

Já vimos como adaptar estilos baseados em adereços. E se quiséssemos ir um pouco mais longe? A função auxiliar CSS ajuda a conseguir isso. Vamos supor que temos dois campos de entrada de texto com estados: vazio e ativo, cada um com uma cor diferente. Nós podemos fazer isso:

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

Está tudo bem. Posteriormente, se precisarmos adicionar outro estado de preenchido, teríamos que modificar nossos estilos:

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

Agora, a operação ternária está crescendo em complexidade. E se adicionarmos outro estado aos nossos campos de entrada de texto mais tarde? Ou se quisermos dar a cada estado estilos adicionais, além da cor? Você pode imaginar apertando os estilos na operação ternária? O ajudante css vem a calhar.

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

O que fizemos foi expandir nossa sintaxe ternária para acomodar mais estilos e com uma sintaxe mais compreensível e organizada. Se a declaração anterior parecer errada, é porque o código está tentando fazer muito. Então, vamos voltar atrás e refinar:

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

Nosso refinamento divide o estilo em três partes diferentes gerenciáveis ​​e fáceis de entender. É uma vitória.

Gerenciador de Folhas de Estilo

Assim como o auxiliar CSS, StyleSheetManager é um método auxiliar para modificar como os estilos são processados. São necessários certos adereços - como disableVendorPrefixes (você pode conferir a lista completa) - que ajudam a desativar os prefixos do fornecedor de sua subárvore.

 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 é passado como prop para <StyleSheetManager> . Portanto, os componentes estilizados envolvidos por <StyleSheetManager> seriam desabilitados, mas não os de <StyledNav> .

Depuração mais fácil

Ao apresentar componentes estilizados para um dos meus colegas, uma de suas queixas foi que é difícil localizar um elemento renderizado no DOM — ou no React Developer Tools, para esse assunto. Essa é uma das desvantagens dos componentes com estilo: ao tentar fornecer nomes de classe exclusivos, ele atribui hashes exclusivos a elementos, que por acaso são enigmáticos, mas torna o displayName legível para facilitar a depuração.

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

Por padrão, os componentes estilizados renderizam o LoginButton como <button class="LoginButton-xxxx xxxx">Login</button> no DOM e como LoginButton no React Developer Tools, o que facilita a depuração. Podemos alternar o booleano displayName se não quisermos esse comportamento. Isso requer uma configuração Babel.

Nota : Na documentação, o pacote babel-plugin-styled-components é especificado, bem como um arquivo de configuração .babelrc . O problema com isso é que, como estamos usando create-react-app , não podemos configurar muitas coisas a menos que ejetemos. É aqui que entram as macros Babel.

Precisaremos instalar babel-plugin-macros com npm ou Yarn e, em seguida, criar um babel-plugin-macros.config.js na raiz do nosso aplicativo, com o conteúdo:

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

Com o valor fileName invertido, o displayName será prefixado com o nome do arquivo para uma precisão ainda mais exclusiva.

Agora também precisamos importar da macro :

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

Conclusão

Agora que você pode compor programaticamente seu CSS, não abuse da liberdade. Se valer a pena, faça o seu melhor para manter a sanidade em seus componentes de estilo. Não tente compor condicionais pesadas, nem suponha que tudo deve ser um componente com estilo. Além disso, não abstraia demais criando componentes de estilo nascentes para casos de uso que você está apenas supondo que estão em algum lugar ao virar da esquina.

Recursos adicionais

  1. Documentação, Componentes Estilizados
  2. “Construindo um sistema de componentes reutilizáveis ​​com React.js e styled-components”, Lukas Gisder-Dube
  3. Uso com Next.js
  4. Uso com Gatsby