Nos aplicativos front-end modernos, a tecnologia CSS-in-JS é bastante popular. O fato é que ele oferece aos desenvolvedores um mecanismo para trabalhar com estilos mais convenientes que o CSS comum. Não me interpretem mal. Eu realmente gosto de CSS, mas criar uma boa arquitetura CSS não é uma tarefa fácil. A tecnologia CSS-in-JS possui algumas
vantagens significativas sobre os estilos CSS convencionais. Infelizmente, porém, o uso do CSS-in-JS pode, em certas aplicações, levar a problemas de desempenho. Neste artigo, tentarei analisar os recursos de alto nível das bibliotecas CSS-in-JS mais populares, falar sobre alguns dos problemas que às vezes surgem ao usá-los e sugerir maneiras de atenuar esses problemas.

Visão geral da situação
Na minha empresa, foi decidido criar uma biblioteca de interface do usuário. Isso nos traria benefícios consideráveis, nos permitiria reutilizar fragmentos padrão de interfaces em vários projetos. Eu fui um dos voluntários que assumiu a tarefa. Decidi usar a tecnologia CSS-in-JS, pois já estava familiarizado com a API de estilo das bibliotecas CSS-in-JS mais populares. No decorrer do trabalho, lutei para agir de maneira razoável. Projetei lógica reutilizável e apliquei propriedades compartilhadas em componentes. Portanto, peguei a composição dos componentes. Por exemplo, o componente
<IconButton />
estendeu o componente
<BaseButton />
, que, por sua vez, era uma implementação de uma entidade
styled.button
simples. Infelizmente, o
IconButton
precisa de um estilo próprio, por isso converti esse componente em um componente estilizado:
const IconButton = styled(BaseButton)` border-radius: 3px; `;
À medida que mais e mais componentes apareciam em nossa biblioteca, usamos cada vez mais operações de composição. Isso não parecia antinatural. Afinal, a composição é, de fato, a base do React. Tudo estava bem até eu criar o componente
Table
. Comecei a sentir que esse componente era renderizado lentamente. Especialmente em situações em que o número de linhas na tabela excedeu 50. Isso estava incorreto. Portanto, comecei a entender o problema recorrendo às ferramentas do desenvolvedor.
A propósito, se você já se perguntou por que as regras CSS não podem ser editadas usando o inspetor de ferramentas do desenvolvedor, saiba que isso ocorre porque elas usam
CSSStyleSheet.insertRule () . Essa é uma maneira muito rápida de modificar folhas de estilo. Mas uma das desvantagens é o fato de que as folhas de estilo correspondentes não podem mais ser editadas pelo inspetor.
Escusado será dizer que a árvore gerada pelo React era realmente enorme. O número de componentes
Context.Consumer
era tão grande que poderia me privar do sono. O fato é que toda vez que um único componente estilizado é renderizado usando
componentes estilizados ou
emoção , além de criar um componente React regular, também é
Context.Consumer
um componente
Context.Consumer
adicional. Isso é necessário para permitir que o script correspondente (a maioria das bibliotecas CSS-JS dependa de scripts executados enquanto a página está em execução) processe corretamente as regras de estilo geradas. Geralmente, isso não causa problemas especiais, mas não devemos esquecer que os componentes devem ter acesso ao tópico. Isso se traduz na necessidade de renderizar
Context.Consumer
adicional para cada elemento estilizado, o que permite "ler" o tema do componente
ThemeProvider
. Como resultado, ao criar um componente estilizado em um aplicativo com um tema, três componentes são criados. Este é um componente
StyledXXX
e mais dois componentes
Context.Consumer
.
É verdade que não há nada particularmente terrível aqui, já que o React faz seu trabalho rapidamente, o que significa que na maioria dos casos não temos nada com que nos preocupar. Mas e se vários componentes estilizados forem montados para criar um componente mais complexo? E se esse componente complexo fizer parte de uma longa lista ou tabela grande em que pelo menos 100 desses componentes são renderizados? Aqui em tais situações, somos confrontados com problemas ...
Criação de perfil
Para testar diferentes soluções CSS-in-JS, criei um aplicativo simples. Ele exibe o texto
Hello World
50 vezes. Na
primeira versão deste aplicativo, envolvi este texto em um elemento
div
regular. No
segundo , usei o componente
styled.div
. Além disso, adicionei um botão ao aplicativo que faz com que todos esses 50 elementos sejam renderizados novamente.
Após renderizar o componente
<App />
, duas árvores React diferentes foram exibidas. As figuras a seguir mostram as árvores de elementos deduzidas pelo React.
Uma árvore exibida em um aplicativo que usa um elemento div regularUma árvore exibida em um aplicativo que usa styled.divEm seguida, usando o botão, renderizei
<App />
10 vezes para coletar dados sobre a carga no sistema, que componentes adicionais do
Context.Consumer
. A seguir, informações sobre a renderização repetida de um aplicativo com elementos
div
regulares no modo de design.
Renderizando novamente o aplicativo com elementos div regulares no modo de design. O valor médio é de 2,54 ms.Renderizando novamente o aplicativo com elementos styled.div no modo de desenvolvimento. O valor médio é de 3,98 ms.O que é muito interessante é que, em média, um aplicativo CSS-in-JS é 56,6% mais lento que o normal. Mas era um modo de desenvolvimento. E o modo de produção?
Renderizando novamente o aplicativo com os elementos div habituais no modo de produção. O valor médio é 1,06 ms.Renderizando novamente o aplicativo com elementos styled.div no modo de produção. O valor médio é 2,27 ms.Quando o modo de produção está ativado, a implementação div do aplicativo parece ser 50% mais rápida em comparação com a mesma versão no modo de desenvolvimento. Um aplicativo styled.div é apenas 43% mais rápido. E aqui, como antes, fica claro que a solução CSS-in-JS é quase duas vezes mais lenta que a solução usual. O que o torna mais lento?
Análise da aplicação durante sua execução
A resposta óbvia à pergunta sobre o que torna um aplicativo CSS-in-JS mais lento pode ser o seguinte: "Foi dito que cem bibliotecas CSS-in-JS processam dois
Context.Consumer
por componente". Mas se você pensar sobre tudo isso
Context.Consumer
, o
Context.Consumer
é apenas um mecanismo para acessar uma variável JS. Obviamente, o React precisa fazer algum trabalho para descobrir onde ler o valor correspondente, mas isso por si só não explica os resultados da medição acima. A resposta real para essa pergunta pode ser encontrada analisando o motivo do uso de
Context.Consumer
. O fato é que a maioria das bibliotecas CSS-in-JS depende de scripts que são executados durante a saída da página no navegador, o que ajuda as bibliotecas a atualizar dinamicamente os estilos dos componentes. Essas bibliotecas não criam classes CSS durante a montagem da página. Em vez disso, eles geram e atualizam dinamicamente tags
<style>
no documento. Isso é feito quando os componentes são montados ou quando suas propriedades são alteradas. Essas tags normalmente contêm uma única classe CSS cujo nome de hash é mapeado para um único componente React. Quando as propriedades desse componente são alteradas, a tag
<style>
correspondente também deve ser alterada. Veja como descrever o que está sendo feito durante esse processo:
- As regras CSS que a tag
<style>
deve ter são geradas novamente. - Um novo nome de classe de hash é criado e usado para armazenar as regras CSS mencionadas acima.
- A propriedade
classname
do componente React correspondente é atualizada para um novo, indicando a classe que acabou de ser criada.
Considere, por exemplo, a biblioteca de
styled-components
. Ao criar o componente
styled.div
biblioteca
atribui um identificador interno (ID) a esse componente e adiciona uma nova marca
<style>
à marca HTML
<head>
. Essa tag contém um único comentário que se refere ao identificador interno do componente React ao qual o estilo correspondente pertence:
<style data-styled-components> </style>
E aqui estão as ações que a biblioteca de componentes com estilo executa ao exibir o componente React correspondente:
- Analisa regras CSS da cadeia de modelo de componentes com estilo.
- Gera um novo nome de classe CSS (ou descobre se deseja manter o nome anterior).
- Executa o pré-processamento de estilos usando stylis.
- Incorpora o CSS resultante do pré-processamento na tag
<style>
correspondente da tag HTML <head>
.
Para poder usar o tópico na etapa 1 deste processo,
é necessário Context.Consumer . Graças a este componente, os valores são lidos a partir do tópico na sequência de modelos. Para poder modificar a marca
<style>
associada a este componente,
Context.Consumer
necessário mais um
Context.Consumer
do componente. Permite acessar uma instância de uma folha de estilo. É por isso que na maioria das bibliotecas CSS-in-JS encontramos duas instâncias de
Context.Consumer
.
Além disso, como todos esses cálculos afetam a interface do usuário, deve-se observar que eles devem ser executados durante a fase de renderização do componente. Eles não podem ser executados no código dos manipuladores de eventos para o ciclo de vida dos componentes React (dessa maneira, sua execução pode ser atrasada e parecerá com a lenta formação de página para o usuário). É por isso que a renderização de aplicativos styled.div é mais lenta que a renderização de um aplicativo comum.
Tudo isso foi observado pelos desenvolvedores de componentes com estilo. Eles otimizaram a biblioteca para reduzir o tempo de re-renderização de componentes. Em particular, a biblioteca descobre se o componente estilizado é "estático". Ou seja, se os estilos do componente dependem do tema ou das propriedades que são passadas para ele. Por exemplo, o componente estático é mostrado abaixo:
const StaticStyledDiv = styled.div` color:red `;
E este componente não é estático:
const DynamicStyledDiv = styled.div` color: ${props => props.color} `;
Se a biblioteca descobrir que o componente é estático, pula as
4 etapas acima, percebendo que nunca precisará alterar o nome da classe gerada (já que não há elemento dinâmico para o qual possa ser necessário alterar as regras CSS associadas a ele). Além disso, nessa situação, a biblioteca
não exibe ThemeContext.Consumer
ao redor do componente estilizado, pois a dependência do tema não permitiria mais que o componente fosse considerado "estático".
Se você for cuidadoso o suficiente ao analisar as capturas de tela apresentadas anteriormente, poderá notar que, mesmo no modo de produção para cada
styled.div
dois componentes
Context.Consumer
são
Context.Consumer
. Curiosamente, o componente que foi renderizado era "estático", pois não havia regras CSS dinâmicas associadas a ele. Em tal situação, seria de esperar que, se este exemplo fosse escrito usando a biblioteca de componentes com estilo, ele não
Context.Consumer
necessário para trabalhar com o tema. A razão pela qual exatamente dois
Context.Consumer
são exibidos aqui é porque o experimento, cujos dados são fornecidos acima, foi realizado usando emoção - outra biblioteca CSS-in-JS. Essa biblioteca tem quase a mesma abordagem que os componentes denominados. As diferenças entre eles são pequenas. Portanto, a biblioteca de emoções analisa a sequência do modelo, processa previamente os estilos usando
stylis e atualiza o conteúdo da
<style>
correspondente. Aqui, no entanto, uma diferença importante deve ser observada entre componentes estilizados e emoção. Consiste no fato de que a biblioteca de emoções
sempre ThemeContext.Consumer
todos os componentes do
ThemeContext.Consumer
- independentemente de usarem o tema ou não (isso explica a aparência da captura de tela acima). É interessante notar que, embora a emoção renderize mais componentes Consumidores do que componentes estilizados, as emoções
superam os componentes estilizados em termos de desempenho. Isso indica que o número de componentes
Context.Consumer
não é um fator importante para diminuir a renderização. Vale ressaltar que, no momento da redação deste material, foi lançada uma versão beta dos componentes com estilo v5.xx que, de acordo com os desenvolvedores da biblioteca,
contorna a emoção em termos de desempenho.
Resuma o que estávamos falando. Acontece que uma combinação de muitos elementos
Context.Consumer
(o que significa que o React precisa coordenar o trabalho de elementos adicionais) e mecanismos internos de estilo dinâmico podem retardar o aplicativo. Devo dizer que todas as tags
<style>
adicionadas à
<head>
para cada componente nunca são excluídas. Isso ocorre porque as operações de remoção de elementos criam uma grande carga no DOM (por exemplo, o navegador precisa reorganizar a página por causa disso). Essa carga é maior que a carga adicional no sistema causada pela presença na página de elementos desnecessários
<style>
. Para ser sincero, não posso dizer com confiança que tags
<style>
desnecessárias podem causar problemas de desempenho. Eles simplesmente armazenam classes não utilizadas geradas durante a operação da página (ou seja, esses dados não foram transmitidos pela rede). Mas você deve saber sobre esse recurso do uso da tecnologia CSS-in-JS.
Devo dizer que as tags
<style>
não criam todas as bibliotecas CSS-in-JS, pois nem todas são baseadas nos mecanismos que operam quando as páginas funcionam nos navegadores. Por exemplo, a biblioteca
linaria não faz nada enquanto a página está sendo executada no navegador.
Ele define um conjunto de classes CSS fixas durante o processo de construção do projeto e ajusta a correspondência de todas as regras dinâmicas na sequência de modelos (ou seja, regras CSS que dependem das propriedades do componente) com propriedades CSS personalizadas. Como resultado, quando uma propriedade de componente é alterada, a propriedade CSS é alterada e a aparência da interface é alterada. Graças a isso, a linaria é muito mais rápida do que as bibliotecas que dependem de mecanismos que operam enquanto as páginas estão em execução. O fato é que, ao usar esta biblioteca, o sistema precisa executar muito menos computação durante a renderização do componente. A única coisa que você precisa fazer ao usar a linaria durante a renderização é lembrar de
atualizar a propriedade CSS customizada. Ao mesmo tempo, porém, essa abordagem é incompatível com o IE11, possui suporte limitado a propriedades CSS populares e, sem configuração adicional, não permite o uso de temas. Como é o caso de outras áreas do desenvolvimento web, entre as bibliotecas CSS-in-JS não existe uma ideal, adequada para todas as ocasiões.
Sumário
A tecnologia CSS-in-JS ao mesmo tempo parecia uma revolução no campo do estilo. Isso facilitou a vida de muitos desenvolvedores e também permitiu, sem esforços adicionais, resolver muitos problemas, como colisões de nomes e o uso de prefixos de fabricantes de navegadores. Este artigo foi escrito para esclarecer a questão de como as bibliotecas CSS-in-JS populares (aquelas que controlam estilos enquanto uma página está em execução) podem afetar o desempenho de projetos da web. Gostaria de chamar atenção especial ao fato de que a influência dessas bibliotecas no desempenho nem sempre leva ao aparecimento de problemas visíveis. De fato, na maioria das aplicações, esse efeito é completamente invisível. Podem ocorrer problemas em aplicativos que possuem páginas que contêm centenas de componentes complexos.
Os benefícios do CSS-in-JS geralmente superam os possíveis efeitos negativos do uso dessa tecnologia. No entanto, as desvantagens do CSS-in-JS devem ser lembradas pelos desenvolvedores cujos aplicativos renderizam grandes quantidades de dados, aqueles cujos projetos contêm muitos elementos de interface em constante mudança. Se você suspeitar que seu aplicativo está sujeito aos efeitos negativos do CSS-in-JS, antes de refatorar, vale a pena tudo para ser avaliado e medido adequadamente.
Aqui estão algumas dicas para melhorar o desempenho do aplicativo que usam as bibliotecas CSS-in-JS populares que fazem seu trabalho quando as páginas são executadas em um navegador:
- Não se empolgue demais com a composição de componentes estilizados. Tente não repetir o erro sobre o qual falei no início e, para criar um botão infeliz, crie uma composição de três componentes estilizados. Se você deseja "reutilizar" o código, use a propriedade CSS e componha as seqüências de caracteres do modelo. Isso permitirá que você faça sem os muitos componentes desnecessários do
Context.Consumer
. Como resultado, o React precisará gerenciar menos componentes, o que aumentará a produtividade do projeto. - Esforce-se para usar componentes "estáticos". Algumas bibliotecas CSS-in-JS otimizam o código gerado se os estilos do componente não dependerem do tema ou das propriedades. Quanto mais "estático" houver nas seqüências de modelo, maior a probabilidade de que os scripts nas bibliotecas CSS-in-JS sejam executados mais rapidamente.
- Tente evitar operações desnecessárias de renderização de seus aplicativos React. Esforce-se para renderizar somente quando você realmente precisar. Graças a isso, nem as ações React nem as ações da biblioteca CSS-in-JS serão carregadas. A nova renderização é uma operação que deve ser executada apenas em casos excepcionais. Por exemplo - com a retirada simultânea de um grande número de componentes "pesados".
- Descubra se uma biblioteca CSS-in-JS é adequada para o seu projeto que não usa scripts que são executados enquanto a página está em execução no navegador. Às vezes, escolhemos a tecnologia CSS-in-JS porque é mais conveniente para o desenvolvedor usá-la, e não várias APIs JavaScript. Mas se seu aplicativo não precisar de suporte se não tiver um uso intensivo de propriedades CSS, é possível que você possa usar uma biblioteca CSS-in-JS como linaria que não use scripts que executam enquanto a página está em execução. Além disso, essa abordagem reduzirá o tamanho do pacote de aplicativos em cerca de 12 Kb. O fato é que o tamanho do código da maioria das bibliotecas CSS-in-JS cabe em 12 a 15 Kb e o código da mesma linaria é menor que 1 Kb.
Caros leitores! Você usa bibliotecas CSS-in-JS?
