Обложка: Лучшие практики стилизованных компонентов React

Лучшие практики стилизованных компонентов React

6

От переводчика

В данной статье, автор Robin Wieruch раскрывает тему использования пакета styled-components в контексте React приложений. Рассматриваются различные подходы к использованию стилизации, приводятся рекомендации, полученные из практического опыта реализации проектов. В переводе используется русский вариант названия styled-components — «стилизованные компоненты».

***

Содержание:

  1. Введение
  2. Совместно расположенные стилизованные компоненты
  3. Импорт стилизованных компонентов как объекта
  4. Один/множество стилизованных компонентов
  5. Пропсы или классы для стилизованных компонентов
  6. Лучшие практики использования пропсов в стилизованных компонентах
  7. Заключение

Введение

При работе нескольких разработчиков над одним React-приложением, всегда полезно нацеливать команду на работу с общим набором лучших практик. Это утверждение также правдиво для стилизации компонентов React. За последние годы мне посчастливилось работать со многими фриланс-разработчиками React над различными приложениями, где нам приходилось выстраивать лучшие практики на протяжении всего проекта. Хотя, конечно, были приложения стилизованные с использованием CSS-in-CSS (например, CSS-модули) или Utility-first CSS (например, Tailwind), вероятность работы со стилизованными компонентами (CSS-in-JS) была довольно высокой, потому что это один из самых популярных стилистических подходов.

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

Совместно расположенные стилизованные компоненты

Самое замечательное в стилизованных компонентах и CSS-in-JS в целом — то, что CSS определен в файлах JavaScript. Начиная работу со стилизованными компонентами вы часто будете просто определять стилизованный компонент рядом с вашим фактическим компонентом React:

const Headline = styled.h1`
  color: red;
`;
 
const Content = ({ title, children }) => {
  return (
    <section>
      <Headline>{title}</Headline>
 
      <span>{children}</span>
    </section>
  );
};

Размещение стилизованных компонентов вместе с вашими фактическими компонентами даёт множество преимуществ. Если вам придётся подключить новых разработчиков React к своему проекту, им будет несложно найти соответствующие определения стилей для каждого компонента. Если вы хотите удалить компонент React, вы легко сможете это сделать. Если вы хотите изменить стиль для фактического компонента, просто перейдите к стилизованному компоненту и настройте его.

До тех пор, пока файл JavaScript остаётся небольшим, можно хранить стилизованные компоненты рядом с фактическими компонентами в одном файле. Некоторые разработчики предпочитают чтобы фактический компонент был вверху, а стилизованный компонент внизу, что возможно из-за «поднятия» (hoisting) JavaScript:

const Content = ({ title, children }) => {
  return (
    <section>
      <Headline>{title}</Headline>
 
      <span>{children}</span>
    </section>
  );
};
 
const Headline = styled.h1`
  color: red;
`;

При увеличении размера файла компонента я и моя команда всегда перемещали стили в другой файл рядом с фактическим файлом компонента. Это всегда отличная возможность вывести структуру папок вашего проекта React на новый уровень. Вы часто будете встречать схожие варианты каталога компонента:

- Section/
--- index.js
--- styles.js

Такой подход по-прежнему помещает стилизованные компоненты рядом с вашим фактическим компонентом, однако теперь они находятся не в одном файле, а в одном каталоге. Следуя этому подходу вы и ваша команда по-прежнему получаете те же преимущества, что и при совместном размещении ваших стилей и компонентов React в одном файле; также есть дополнительные преимущества.

Во-первых, меньше помех для разработчиков React, потому что они могут сосредоточиться либо на деталях реализации фактического компонента, либо на его стиле. Во-вторых, это помогает разработчикам, которые привыкли работать с CSS-in-CSS, принять стратегию работы с CSS-in-JS, по-прежнему работая с совместно размещенными файлами стилей. В-третьих, разработчики могут сохранять высокую степень уверенности в своем коде и в том, что они не нарушают другие стили, потому что всё хранится в одном каталоге компонентов.

В качестве примечания: если вы встретите дубликаты стилей в стилизованных компонентах, рассмотрите возможность использования служебной функции css стилизованных компонентов:

import styled, { css } from 'styled-components';
 
const red = css`
  color: red;
`;
 
const Headline = styled.h1`
  ${red}
 
  font-size: 20px;
`;
 
const Text = styled.p`
  ${red}
 
  font-size: 16px;
`;

И последнее, но не менее важное: что должно произойти, если вы захотите использовать стилизованный компонент в нескольких фактических компонентах? Ответ такой же, как и для любого другого компонента React: сохраняйте стили в каталоге более высокого уровня, откуда он может быть импортирован более чем одной папкой компонента. Если вы меняете стиль стилизованного компонента, проверьте все фактические компоненты которые его импортируют. Если ни один компонент больше не использует стиль, удалите стилизованный компонент из каталога верхнего уровня. Если вы хотите использовать глобальные стили, вы можете применить служебную функцию стилизованных компонентов createGlobalStyle.

Импорт стилизованных компонентов как объекта

Размещение стилизованных компонентов в дополнительном файле JavaScript рядом с файлом фактического компонента в конечном итоге становится обычной практикой для растущих приложений React. Следующий шаблон для импорта стилизованных компонентов должен быть хорошо знаком разработчикам:

import { Headline } from './styles';
 
const Content = ({ title, children }) => {
  return (
    <section>
      <Headline>{title}</Headline>
 
      <span>{children}</span>
    </section>
  );
};

Здесь важно то, что стилизованные компоненты из совместно расположенного файла обычно используют именованный экспорт, чтобы их можно было импортировать с помощью именованного импорта. Однако, хотя стилизованные компоненты следует рассматривать просто как компоненты React, не всегда легко обнаружить стилизованный компонент в JSX-разметке растущего компонента React.

Импорт всего содержимого файла как объекта часто имеет больше преимуществ:

import * as Styled from './styles';
 
const Content = ({ title, children }) => {
  return (
    <section>
      <Styled.Headline>{title}</Styled.Headline>
 
      <span>{children}</span>
    </section>
  );
};

Разработчики стремятся импортировать все свои стили либо с помощью имени Styled, либо в еще более краткой форме, установленной в соглашениях об именах:

import * as S from './styles';
 
const Content = ({ title, children }) => {
  return (
    <section>
      <S.Headline>{title}</S.Headline>
 
      <span>{children}</span>
    </section>
  );
};

Импорт стилизованных компонентов таким образом дает три преимущества: во-первых, ваш оператор импорта простой, краткий и везде одинаковый. Во-вторых, чтобы предоставить новый стилизованный компонент из вашего файла стилей требуется только именованный экспорт из этого файла. И в-третьих, при согласовании командой общей структуры именования для объектов стилей, стилизованные компоненты в JSX-разметке вашего компонента React становятся более очевидными.

В заключение, при импорте стилизованных компонентов с именованным импортом часто проект придёт к тому, что будет несколько соглашений об именах для таких компонентов (обычно называемых StyledHeadline или Headline), которые не всегда согласуются друг с другом. Если ваша команда с самого начала придерживается одного соглашения об именах, плохих подходов легче избежать при импорте всего содержимого файла, чем при импорте каждого компонента по отдельности.

Один/множество стилизованных компонентов

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

В левой части спектра находится подход, когда все, что связано со стилем, становится стилизованным компонентом. Таким образом, каждый стилизованный компонент отвечает за свой стиль.

const Section = styled.section`
  border-bottom: 1px solid grey;
  padding: 20px;
`;
 
const Headline = styled.h1`
  color: red;
`;
 
const Text = styled.span`
  padding: 10px;
`;
 
const Content = ({ title, children }) => {
  return (
    <Section>
      <Headline>{title}</Headline>
 
      <Text>{children}</Text>
    </Section>
  );
};

Обычно это самый популярный подход, и я думаю, что в основном потому, что разработчики больше принимают JavaScript, чем CSS. Таким образом, использование только стилизованных компонентов без необходимости использования классов CSS или селекторов CSS упрощает задачу. Кроме того, он поддерживает установку, что «все является компонентом».

На другой стороне спектра несколько сторонников согласились с использованием только одного корневого компонента (обычно называемого Container или Wrapper), а все остальное становится CSS. Обычно этот подход предпочитают разработчики более опытные в CSS, поскольку они используют все преимущества CSS (и его расширений). Он также сохраняет JSX-разметку более чистой с помощью использования HTML (семантически) и CSS вместо повсеместного использования компонентов.

const Container = styled.section`
  border-bottom: 1px solid grey;
  padding: 20px;
 
  h1 {
    color: red;
  }
 
  .text {
    padding: 10px;
  }
`;
 
const Content = ({ title, children }) => {
  return (
    <Container>
      <h1>{title}</h1>
 
      <span className="text">{children}</span>
    </Container>
  );
};

Однако этот подход может быть более подвержен ошибкам, потому что соответствие стилей больше не является таким явным. В то время как среда разработки будет уведомлять вас, если вы используете стилизованный компонент, который не определен, вы не узнаете, что у вас есть опечатка в ваших селекторах CSS. Кроме того, для таких инструментов, как линтинг или подсветка кода, становится сложнее обнаружить некорректный или неиспользуемый CSS.

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

Пропсы или классы для стилизованных компонентов

Ранее я упоминал, что разработчики больше склоняются к использованию JavaScript, чем CSS. Вы часто можете заметить это, когда пропсы React или класс CSS используются также для стилизованного компонента. Давайте рассмотрим следующий пример, где мы могли бы использовать пропсы или класс.

Мы начнем с класса CSS:

import styled from 'styled-components';
import cs from 'classnames';
 
...
 
const Text = styled.span`
  padding: 10px;
 
  &.invalid {
    text-decoration: line-through;
  }
`;
 
const Content = ({ title, isStrikeThrough, children }) => {
  return (
    <Section>
      <Headline>{title}</Headline>
 
      <Text className={cs({ invalid: isStrikeThrough })}>
        {children}
      </Text>
    </Section>
  );
};

В качестве примера использования пропсов React, рассмотрим код ниже:

...
 
const Text = styled.span`
  padding: 10px;
 
  text-decoration: ${(props) =>
    props.invalid ? 'line-through' : 'none'};
`;
 
const Content = ({ title, isStrikeThrough, children }) => {
  return (
    <Section>
      <Headline>{title}</Headline>
 
      <Text invalid={isStrikeThrough}>{children}</Text>
    </Section>
  );
};

Любой способ работает, и ваша команда должна решить, что лучше подходит для вас и проекта. Однако мне нравится применять первый подход с использованием класса CSS, хотя он кажется менее популярным и даже несмотря на то, что для его чистоты часто требуется служебная библиотека имён классов.

Тем не менее, использование класса CSS даёт преимущество, заключающееся в том, что CSS больше соответствует его изначальной природе. Если в вашей команде есть разработчики, которые хорошо разбираются в CSS или больше привыкли работать с JavaScript и CSS ещё до появления React, подумайте о том, чтобы использовать именно такой подход. Использование пропсов React для CSS-in-JS тесно связано с тем, как всё работает в мире React и не так нелегко переносится в другие среды.

В конце концов, я не против использования пропсов React для стилей, я просто за их использование в определённых ситуациях. Я бы рекомендовал использовать пропсы только если необходим динамический стиль:

const Headline = styled.h1`
  color: ${(props) => props.color};
`;
 
const Text = styled.span`
  padding: 10px;
 
  &.invalid {
    text-decoration: line-through;
  }
`;
 
const Content = ({ title, isStrikeThrough, color, children }) => {
  return (
    <Section>
      <Headline color={color}>{title}</Headline>
 
      <Text className={cs({ invalid: isStrikeThrough })}>
        {children}
      </Text>
    </Section>
  );
};

Последний пример ясно показывает, как команда разработчиков может выбирать, когда использовать класс CSS, а когда использовать пропсы React. Класс CSS можно использовать, когда он используется постоянно или когда его можно переключить с помощью флага булево. Но если что-то не может быть определено классом CSS, например цвет, можно использовать пропсы.

Лучшие практики использования пропсов в стилизованных компонентах

Я вижу несколько лучших практик использования пропсов для динамического стиля в стилизованных компонентах. Во-первых, используйте короткое имя параметра или сразу же деструктурируйте его. Здесь применяются те же правила, что и для функциональных компонентов в React, потому что пропсы почти никогда не используются напрямую, вместо этого мы хотим использовать их содержимое:

const Headline = styled.h1`
  color: ${(p) => p.color};
`;
 
const Text = styled.span`
  padding: ${({ padding }) => padding}px;
`;

Далее желательно использовать временные свойства со стилизованными компонентами, потому что они приносят нам двойную пользу: во-первых, такой подход использует пропс только в стилизованном компоненте, и таким образом пропс не будет передан HTML-элементу как атрибут. Во-вторых, такой подход явно даёт понять каждому разработчику, при чтении JSX React, какие пропсы используются стилизованным компонентом, а какие пропсы использует DOM:

const Button = styled.button`
  color: ${(p) => p.$color};
`;
 
const ClickMe = ({ color, disabled, onClick }) => {
  return (
    <Button
      $color={color}
      disabled={disabled}
      onClick={onClick}
    >
      Click Me
    </Button>
  );
};

И последнее, но не менее важное: используйте полиморфные свойства, если вы хотите изменить HTML-элемент, лежащий в основе вашего стилизованного компонента. Например, для кнопки которая должна быть просто ссылкой на другой URL-адрес, вы можете, в зависимости от условий, назначить другой элемент HTML:

const ClickMe = ({ to = '', onClick = () => {} }) => {
  return (
    <ButtonOrLink
      as={to ? 'a' : 'button'}
      to={to}
      onClick={onClick}
    >
      Click Me
    </ButtonOrLink>
  );
};

В API стилизованных компонентов есть несколько скрытых ошибок, поэтому учтите их все, чтобы получить максимум пользы. Также имеет смысл изучить особенности вместе со всей командой, чтобы выработать согласованные способы работы.

Заключение

Как вы поняли, не существует единственного правильного способа использования стилизованных компонентов, однако, как только вы подберёте свои варианты, вы можете более взвешенно решать, как применить их в вашем проекте React. Чаще всего решения действительно зависят от разработчиков, являются ли они сторонниками CSS или больше склоняются к JavaScript.

И последнее, но не менее важное: вы можете использовать улучшенный вариант отладки стилизованных компонентов. После его включения инструменты разработчика в браузере покажут вам displayName компонента, прикрепленного к классу CSS элемента. Поэтому каждому разработчику станет легче определить, какой стилизованный компонент фактически используется в скомпилированном HTML.

Хинт для программистов: если зарегистрируетесь на соревнования Huawei Cup, то бесплатно получите доступ к онлайн-школе для участников. Можно прокачаться по разным навыкам и выиграть призы в самом соревновании.

Перейти к регистрации

Styled Components Best Practices

Что думаете?