CSS-in-JS и React Server Components

Почему некоторые решения CSS-in-JavaScript не могут работать в новом серверном мире React — и что с этим делать.

Введение

С эволюцией экосистемы React, особенно с переходом Next.js к подходу "server-first", приложения, использующие решения CSS-in-JS, такие как Emotion и styled-components, сталкиваются с серьезными проблемами. Эти библиотеки - CSS-in-JS по своей природе несовместимы с новой парадигмой React.
Хотя можно предположить, что эти библиотеки могут быть доработаны для соответствия новому подходу, важно помнить, что они поддерживаются волонтерами и ждать от них полной переработки, чтобы адаптировать их к изменениям в React — это сложная и, возможно, нереалистичная задача.
Давайте рассмотрим, почему некоторые решения CSS-in-JS не могут работать в новом серверном мире, а затем познакомимся с библотекой CSS-in-JS - Linaria, которая использует практически идентичный со styled-components API.

Парадигма RSC

Для начала важно понять, как работал React до появления Server Components (до RSC) и как он работает сейчас (после RSC).

До RSC: как на сервере, так и на клиенте

До RSC, React рендерил компонент как на сервере, так и на клиенте. React всегда рендерил статические части HTML-страницы на сервере и отправлял их клиенту для "гидратации". Этап гидратации — это клиентский JavaScript, который отвечает за работу с ссылками или прикрепление обработчиков событий, что позволяет обеспечить интерактивность.

После RSC: только на сервере

Однако теперь после RSC все компоненты React по умолчанию являются серверными. React рендерит статические части HTML-страницы, но не гидратирует их на клиенте, что снижает/устраняет любой клиентский JavaScript. Это интересный подход, так как после RSC мы можем делать серверные запросы из любого компонента на любом уровне дерева, а не только на уровне маршрута, как это было ранее в Next.js.

Новая директива React Use Client

Вы все еще можете включить клиентский JavaScript и методы React, такие как useState, useEffect и т.д., но вы должны явно добавить директиву "use client" в начало файла, чтобы React вел себя как обычно.

Почему CSS-in-JS не работает с RSC?

Для понимания этого вопроса важно понимать, как работают "устаревшие" библиотеки CSS-in-JS.
Такие библиотеки, как Emotion и styled-components, выполняют большую часть работы в режиме реального времени на клиенте с использованием клиентского JavaScript, который, как мы установили, больше не существует в RSC, или "серверных" компонентах.
Если вы видели приложение, использующее styled-components, вы, вероятно, замечали странные названия классов.

<html> <body> <a class="sc-4c0ad8fd-0 hcJJXU"> styled link </a> </body> </html>

Это происходит потому, что со styled-components вы не пишете CSS-классы, вы пишете компоненты, библиотека выполняет извлечение стилей, преобразование их в CSS, который понимает браузер, и генерирует случайное, но уникальное имя класса, а затем применяет его к DOM-элементу для их связывания.

Все не так плохо

Существуют новые решения этой проблемы, которые перемещают основную работу с клиента на этап сборки. Мы конвертируем TypeScript в "браузерный JS" на этапе сборки поэтому выполнение CSS-in-JS на этом этапе кажется отличным решением для RSC.
Однако здесь сразу возникает логичный вопрос: не придется ли разработчикам по всему миру переписывать все свои объявления styled(точка) с использованием новомодного API? Согласитесь, звучит жутковато.
К счастью, создатели Linaria, которая существует с 2017 года, выбрали очень похожий API на styled-components. Pigment CSS от команды MUI последовал аналогичным путем.

Как работают библиотеки CSS-in-JS, совместимые с RSC?

В случае с Linaria она использует CSS-модули. Если использовать пример styled.a:

import styled from '@linaria/react'; const SomeComponent = () => { return ( <ButtonLink> styled link </ButtonLink> ); } const ButtonLink = styled.a` background: transparent; border-radius: 3px; border: 1px solid var(--accent-color); color: var(--accent-color); display: inline-block; margin: 0.5rem 1rem; padding: 0.5rem 0; transition: all 200ms ease-in-out; width: 11rem; `; export default SomeComponent;

Вместо создания случайного, но уникального имени класса, Linaria создает файл [component name].module.css и преобразует объявление styled в реальный CSS-класс, например:

/* src/components/some-component/SomeComponent.module.css */ .ButtonLink { background: transparent; border-radius: 3px; border: 1px solid var(--accent-color); color: var(--accent-color); display: inline-block; margin: 0.5rem 1rem; padding: 0.5rem 0px; transition: all 200ms ease-in-out 0s; width: 11rem; };

На этапе сборки вашего приложения импорт в CSS-модуль .css будет добавлен в компонент, и объявление styled будет преобразовано в основной HTML-элемент (в данном случае это элемент якоря), и будет добавлена ссылка на класс:

import styles from './SomeComponent.module.css'; const SomeComponent = () => { return ( <a className={styles.ButtonLink}> styled link </a> ); } export default SomeComponent;

Поскольку все это происходит на этапе сборки, браузеру ничего не нужно делать — и, следовательно, не требуется клиентский JavaScript. Это действительно отличное решение проблемы, и все, что нужно для миграции, это удалить styled-components, установить Linaria и выполнить поиск и замену с

import styled from 'styled-components';

на

import styled from '@linaria/react';

Итог

Когда что-то, столь широко используемое, как React, полностью меняет свой основной принцип работы, многие проекты с открытым исходным кодом, разработанные для него, также должны измениться — или, в некоторых случаях, остаться в истории. В мире JavaScript все меняется часто, и все, что мы можем сделать, это стараться не отставать!

22
1 комментарий

Слушай, прикольно. упустил немного этот момент и не думал про такую проблему с styled-components в Next =) спасибо!

Ответить