React + CSS Variables + ShadowDOM + styled-components

Distribuir componentes e estilo sem grilo

pexels.com

Há algum tempo que trabalho com React. Uma library que mudou meu “mindset” em relação a forma como eu enxergava engenharia de sofware principalmente no lado do cliente (client-side). Confesso que, inicialmente, estava um tanto relutante e cético quanto ao seu uso e tecnologias relacionadas que, pra mim, eram desconfortáveis aos olhos (JSX, seus métodos verbosos de "lifecycle", carregar CSS dentro do JS (webpack) etc).

Por fim, percebi também que eu estava um pouco resistente a mudanças, até porque, antes do React, eu estava trabalhando com Angular — Tinha acabado de comprar um livro — e como primeira opção, eu estava "inclinado" a escolhê-lo (Angular) até por conta da "zona de conforto". Armadilhas da nossa cabeça que felizmente superei.

Após a fase de adaptação, começaram a surgir cada vez mais feedbacks fomentando o pensamento crítico. Afinal, o time era composto por membros “inquietos”, pessoas inovadoras, criativas e logo surgiram projetos como React Atellier, React Entity (hoje chamado Speck).

Como sempre, nem tudo são flores

Ok. Após quase 2 anos tudo faz muito sentido, me sinto mais confiante com a ferramenta, mas, será que tudo está bem resolvido?

Aqui proponho uma reflexão sobre alguns pontos que ainda assolam não só a mim, mas também boa parte da galera iniciante no "mundo React" e por vezes até mesmo os mais experientes.

  • Como entregar estilo de forma confiável e eficiente?
  • Como proteger seu componente de estilos globais (encapsular)?
  • Como estilizar ou estender estilos em componentes externos (libraries) ex. Datepicker?

Antes de tentar propor uma solução, quero deixar claro que não tenho a intenção de "criar um padrão" ou mesmo "impor" qualquer ideia. O intuito é provocar uma discussão e problematizar algo que muitas vezes é negligenciado.

Cenário

Recentemente, desenvolvi um componente que seria utilizado num sistema que tem aproximadamente 4 anos de existência (bem antigo). O sistema foi inicialmente feito com Bootstrap, Backbone, reset.css entre outras tecnologias e ficou resolvido que seria refatorado aos poucos com React.

Great!!

Como de costume, usei o React Storybook como ambiente de desenvolvimento — prática comum no time e até aquele momento, nunca tivemos problemas.

O componente tinha elementos de formulário (input, select…) e assim que terminei, solicitei ao dev que fizesse o teste e fiquei assustado quando vi o componente completamente "quebrado". Os inputs totalmente diferentes do Storybook.


Depurando o componente, percebi que ali, havia ALGUNS problemas;

  • CSS global do Bootstrap e Reset CSS
  • O projeto não usava BEMCSS ou SMACSS (lembra-se dos 4 anos?)
  • Os forms foram feitos com react-select (inclusive o meu componente) que não tem uma API tão legal pra estender estilos, a library te obriga a sobrescrever a classe globalmente, ex. .my-namespace .Select-option{}
  • Outro problema era que alguém já havia mudado os estilos do react-select globalmente, ou seja, qualquer lugar que eu usasse aquele componente, herdaria o estilo "sobrescrito" anteriormente — Tudo errado.

Diante desse cenário caótico, fiquei um pouco "amarrado" e comecei a ponderar possíveis soluções que estariam ao meu alcance;

  • Boostrap e reset CSS não consigo remover tão brevemente.
  • Usar BEMCSS está fora de cogitação, pois levaria muito tempo pra refatorar todo sistema.
  • Remover o react-select não resolveria, afinal o reset e Bootstrap estava aplicando estilo sobre elementos, ex input, select, h1, h2.

Não encontrei nenhuma "saída mágica" que pudesse resolver o problema de maneira confiável, viável e que não levasse tanto tempo.

A reflexão

Naquele dia, fui inconformado pra casa pensando: como um problema tão recorrente e simples acontece ainda hoje?

A primeira abordagem foi colocar a culpa no "sistema antigo" que não usava as boas práticas citadas acima que tanto se prega diariamente nos foruns e blogs de desenvolvimento afora. Mas então percebi que o problema sempre existiu, sempre vai existir e a única forma de resolvê-lo, é mudar a forma como eu estava construindo meus componentes.

Pesquisando um pouco, encontrei uma galera que também está discutindo melhor como entregar estilo nos componentes de forma mais eficiente.

A Journey toward better style.

Vale a penas ler. Continuando…

A Proposta

Talk is cheap. Show me the code — Linus Torvalds

Pensando nos problemas relatados acima, eu criei um React componente usando algumas tecnologias relativamente novas, mas que já estão amplamente suportadas pela maior parte dos browsers ou oferecem formas de fazer "fallback" para os mais antigos.


React Styled Select

Shadow DOM

Shadow DOM provides encapsulation for DOM and CSS in a Web Component. Shadow DOM makes it so these things remain separate from the DOM of the main document. You can also use Shadow DOM by itself, outside of a web component.

O Shadow DOM é um excelente recurso onde o browser cria uma sub-árvore protegida que não herda os estilos (CSS) da árvore pai. Ou seja, é criado uma camada de proteção no componente e ainda implica em um ganho de performance adicional pelo fato do navegador não precisar fazer "repaint" da página toda sempre que aquela área da tela (ShadowDOM) onde o componente foi montado sofrer alguma mudança. Saiba mais sobre ShadowDOM (aqui).

Para ilustrar nossa ideia, declarei um trecho de CSS que aplica globalmente ao elemento input, background e borda.

style.css

imagem 1

Resultado

imagem 2

O primeiro elemento é afetado diretamente com a declaração de estilo global que eu fiz. Já o segundo, não sofre mudanças. Isso é maravilhoso!

imagem 3

Quando inspecionamos o DOM, podemos ver o ShadowDOM em ação (imagem 3).

Polymer

Polymer is a JavaScript library that helps you create custom reusable HTML elements, and use them to build performant, maintainable apps.

Acredito que a maioria de vocês conhecem o projeto Polymer. Ele é um dos pioneiros em usar tecnologias/conceitos como ShadowDOM/Web-components, Service Workers, PWA e quando eu particularmente comecei a ouvir sobre esses temas, o projeto Polymer já existia um tempão.

Mas agora você deve estar se perguntando como eu implementei ShadowDOM no React, certo? Bom, por sorte alguém já sentiu a necessidade de usá-lo antes de mim e fez o react-shadow, uma library extremamente simples e fácil de usar. Eu nem precisei sofrer... hehe

React Shadow

Maravilha! Resolvido parte do problema de contaminação por estilo global.

CSS Variables

CSS Variables are entities defined by CSS authors which contain specific values to be reused throughout a document. They are set using custom property notation (e.g. --main-color: black; ) and are accessed using the var() function (e.g. color: var(--main-color); ).

Demorei um certo tempo pra olhar este recurso como uma opção na minha "caixa de ferramentas", mas recentemente percebi que já passou da hora e no react-styled-select eu vi uma oportunidade perfeita para usá-lo.

Se você já trabalhou com react-select percebeu que eles não tem (ainda) suporte para "class prefix/namespace", ou mesmo passar classNames profundamente no componente, portanto você só consegue mudar algum estilo se aplicar diretamente sobre as classes (prática condenável no BEMCSS, SMACSS).

Aí que entra CSS Variables. Ao invés de um ter que passar className em "níveis profundos", eu simplesmente permito estender o estilo do componente com CSS Variables.

Exemplo

Se eu quiser aplicar um estilo especificamente para classe ".dark-theme", ficaria assim;

Mais informações sobre: CSS Variables Fallback.

Vamos a próxima tecnologia (uma library)…

styled-components

Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress

O styled-components é uma das opções "css-modules like" disponíveis. Existem outras soluções mas particularmente gostei dele por ter uma sintaxe bem fácil (es6 template string) e ser pouco verboso. Além disso, tem uma comunidade bem ampla e ativa. Mas vai da escolha pessoal de cada um escolher entre outros como Aphrodite, JSS, Fela, CSS Modules.

Existem alguns sites que ajudam a fazer comparação entre essas libraries. Um deles é o libhunt.

js.libhunt.com

Entre os diversos benefícios, essa library foi usada para que eu não precise me preocupar com nomes de classes, assim não corre o risco daquela classe específica ter sido usada anteriormente e o componente sofrer alguma "contaminação" de CSS caso você não estiver usando o recurso de ShadowDOM. E também para que seja evitada declaração de estilo diretamente sobre a classe do componente como acontece no react-select ex. my-namespace .Select-control {}.

Note: A library não resolve estilos aplicados diretamente sobre tags, portanto se o programador "estilizar" um input ainda terá problema a não ser que usar ShadowDOM.

Imagem 4

As classes acima (image 4) são internas do meu componente, portanto não faz sentido o projeto (seu site ou sistema) usá-las para alterar o estilo default. Mas ainda sim é possível informar minhas classes específicas em cada elemento do componente. Veja…

Resultado

imagem 5

Conclusão

Espero ter deixado claro os problemas que estou propondo resolver e também a solução proposta. Toda ideia ainda está sendo construída e cabe a nós, discutir e aperfeiçoá-la juntos. Como citei anteriormente, a intenção é provocar a discussão.

E por fim, o componente react-styled-select não deve ser visto como foco do artigo e sim uma library que exemplifica/ilustra a ideia de um modo geral.

Valeu, galera!

Show your support

Clapping shows how much you appreciated Bruno Agutoli’s story.