Как использовать стилизованные компоненты в React
Опубликовано: 2022-03-10Стилизованные компоненты — это инструмент CSS-in-JS, который устраняет разрыв между компонентами и стилем, предлагая множество функций, позволяющих настроить и запустить компоненты стилей функциональным и многоразовым способом. В этой статье вы узнаете об основах стилизованных компонентов и о том, как правильно применять их в своих приложениях React. Прежде чем приступить к изучению этого руководства, вы должны были работать с React ранее. Если вы ищете различные варианты стилизации компонентов React, вы можете ознакомиться с нашим предыдущим постом на эту тему.
В основе CSS лежит возможность ориентироваться на любой элемент HTML — глобально — независимо от его положения в дереве DOM. Это может быть препятствием при использовании с компонентами, потому что компоненты в разумной степени требуют совместного размещения (т. е. хранения активов, таких как состояния и стили), ближе к тому месту, где они используются (известное как локализация).
По собственным словам React, стилизованные компоненты — это « визуальные примитивы для компонентов », и их цель — предоставить нам гибкий способ стилизации компонентов. Результатом является тесная связь между компонентами и их стилями.
Примечание. Стилизованные компоненты доступны как для React, так и для React Native, и хотя вам обязательно следует ознакомиться с руководством по React Native, здесь мы сосредоточимся на стилизованных компонентах для React.
Почему стилизованные компоненты?
Помимо помощи в определении области действия стилей, стилизованные компоненты включают в себя следующие функции:
- Автоматическое добавление префикса поставщика
Вы можете использовать стандартные свойства CSS, а стилизованные компоненты добавят префиксы поставщиков, если они понадобятся. - Уникальные имена классов
Стилизованные компоненты не зависят друг от друга, и вам не нужно беспокоиться об их именах, потому что библиотека сделает это за вас. - Устранение мертвых стилей
Стилизованные компоненты удаляют неиспользуемые стили, даже если они объявлены в вашем коде. - и многое другое.
Установка
Установить стилизованные компоненты очень просто. Вы можете сделать это через CDN или с помощью менеджера пакетов, такого как Yarn…
yarn add styled-components
… или нпм:
npm i styled-components
В нашей демонстрации используется приложение create-реагировать.
Начинающийся
Возможно, первое, что вы заметите в стилизованных компонентах, — это их синтаксис, который может быть обескураживающим, если вы не понимаете волшебства, скрывающегося за стилизованными компонентами. Короче говоря, стилизованные компоненты используют литералы шаблонов JavaScript для преодоления разрыва между компонентами и стилями. Итак, когда вы создаете стилизованный компонент, на самом деле вы создаете компонент React со стилями. Это выглядит так:
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>; }
Здесь StyledButton
— это стилизованный компонент, и он будет отображаться как кнопка HTML с содержащимися стилями. styled
— это внутренний служебный метод, который преобразует стили из JavaScript в настоящий CSS.
В необработанном HTML и CSS у нас было бы это:
button { background-color: black; font-size: 32px; color: white; } <button> Login </button>
Если стилизованные компоненты являются компонентами React, можем ли мы использовать свойства? Да мы можем.
Адаптация на основе реквизита
Стилизованные компоненты функциональны , поэтому мы можем легко динамически стилизовать элементы. Предположим, у нас есть два типа кнопок на нашей странице, одна с черным фоном, а другая с синим. Нам не нужно создавать для них два стилизованных компонента; мы можем адаптировать их стиль на основе их реквизита.
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> ) }
Поскольку StyledButton
— это компонент React, который принимает реквизиты, мы можем назначить другой цвет фона в зависимости от наличия или значения реквизита bg
.
Однако вы заметите, что мы не присвоили нашей кнопке type
. Давайте сделаем это:
function Profile() { return ( <> <StyledButton bg="black" type="button"> Button A </StyledButton> <StyledButton bg="blue" type="submit" onClick={() => alert("clicked")}> Button B </StyledButton> </> ); }
Стилизованные компоненты могут различать типы свойств, которые они получают. Они знают, что type
является атрибутом HTML, поэтому фактически отображают <button type="button">Button A</button>
, используя bg
в своей собственной обработке. Обратите внимание, как мы прикрепили обработчик событий?
Говоря об атрибутах, расширенный синтаксис позволяет нам управлять реквизитами с помощью конструктора attrs
. Проверь это:
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"}; `;
Заметили, что нам не нужна тройка при установке ширины? Это потому, что мы уже установили для него width: props.width || "100%",
width: props.width || "100%",
. Кроме того, мы использовали настраиваемые свойства CSS, потому что можем!
Примечание. Если стилизованные компоненты являются компонентами React, и мы можем передавать реквизиты, то можем ли мы также использовать состояния? Учетная запись библиотеки GitHub имеет проблему, касающуюся именно этого вопроса.
Расширение стилей
Допустим, вы работаете над целевой страницей и установили для своего контейнера определенную максимальную ширину, чтобы все было по центру. Для этого у вас есть StyledContainer
:
const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `;
Затем вы обнаружите, что вам нужен контейнер меньшего размера с отступом 10 пикселей с обеих сторон вместо 20 пикселей. Вашей первой мыслью может быть создание другого компонента со стилями, и вы будете правы, но не потребуется много времени, чтобы понять, что вы дублируете стили.
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; `;
Прежде чем приступить к созданию StyledSmallContainer
, как в приведенном выше фрагменте кода, давайте узнаем, как повторно использовать и наследовать стили. Это более или менее похоже на то, как работает оператор spread
:
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> ); }
В вашем StyledSmallContainer
вы получите все стили из StyledContainer
, но отступы будут переопределены. Имейте в виду, что обычно вы получаете элемент section, отображаемый для StyledSmallContainer
, потому что это то, что визуализирует StyledContainer
. Но это не значит, что она высечена в камне или неизменна.
Полиморфная опора as
С помощью полиморфной опоры as
вы можете поменять местами отображаемый конечный элемент. Один из вариантов использования — это когда вы наследуете стили (как в последнем примере). Если, например, вы предпочитаете div
section
для StyledSmallContainer
, вы можете передать свойство as
вашему стилизованному компоненту со значением предпочитаемого вами элемента, например так:
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> ); }
Теперь StyledSmallContainer
будет отображаться как div
. Вы даже можете иметь пользовательский компонент в качестве значения:
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> ); }
Не принимайте это как должное.
SCSS-подобный синтаксис
Препроцессор CSS Stylis позволяет стилизованным компонентам поддерживать синтаксис, подобный SCSS, например вложенность:
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> ); }
Анимация
Стилизованные компоненты имеют помощник по keyframes
, который помогает создавать (повторно используемые) ключевые кадры анимации. Преимущество здесь в том, что ключевые кадры будут отделены от стилизованных компонентов и могут быть экспортированы и повторно использованы везде, где это необходимо.
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; `;
Глобальный стиль
Хотя первоначальной целью CSS-in-JS и, соответственно, стилизованных компонентов является определение области действия стилей, мы также можем использовать глобальные стили стилизованных компонентов. Поскольку мы в основном работаем со стилями с заданной областью действия, вы можете подумать, что это неизменная заводская настройка, но вы ошибетесь. Подумайте об этом: что на самом деле представляет собой определение масштаба? Технически мы можем — во имя глобального стиля — сделать что-то подобное:
ReactDOM.render( <StyledApp> <App /> </StyledApp>, document.getElementById("root") );
Но у нас уже есть вспомогательная функция — createGlobalStyle
— единственной целью существования которой является глобальная стилизация. Так зачем же отрицать его ответственность?
Мы можем использовать createGlobalStyle
для нормализации CSS:
import {createGlobalStyle} from "styled-components"; const GlobalStyle = createGlobalStyle` /* Your css reset here */ `; // Use your GlobalStyle function App() { return ( <div> <GlobalStyle /> <Routes /> </div> ); }
Примечание. Стили, созданные с помощью createGlobalStyle
, не имеют дочерних элементов. Узнайте больше в документации.
На этом этапе вам может быть интересно, почему мы вообще должны использовать createGlobalStlye
. Вот несколько причин:
- Без него мы не можем настроить таргетинг на что-либо вне корневого рендера (например,
html
,body
и т. д.). -
createGlobalStyle
внедряет стили, но не отображает никаких реальных элементов. Если вы внимательно посмотрите на последний пример, то заметите, что мы не указали какой-либо HTML-элемент для отображения. Это круто, потому что на самом деле нам может не понадобиться этот элемент. В конце концов, нас интересуют глобальные стили. Мы ориентируемся на селекторы в целом, а не на конкретные элементы. -
createGlobalStyle
не имеет области действия и может отображаться в любом месте нашего приложения и будет применяться, пока он находится в DOM. Думайте о концепции , а не о структуре .
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> ); }
Если вы думаете о структуре, то app-title
не должен иметь стиль, заданный в GlobalStyle
. Но это не так. Где бы вы ни выбрали для рендеринга GlobalStyle
, он будет внедрен при рендеринге вашего компонента.
Будьте осторожны : createGlobalStyles
будет отображаться только тогда, когда он находится в DOM.
Помощник CSS
Мы уже видели, как адаптировать стили на основе реквизита. Что, если мы хотим пойти немного дальше? Вспомогательная функция CSS помогает достичь этого. Предположим, у нас есть два поля ввода текста с состояниями: пустое и активное, каждое из которых имеет свой цвет. Мы можем это сделать:
const StyledTextField = styled.input` color: ${(props) => (props.isEmpty ? "none" : "black")}; `;
Все хорошо. Впоследствии, если нам нужно добавить еще одно состояние заполнения, нам придется изменить наши стили:
const StyledTextField = styled.input` color: ${(props) => props.isEmpty ? "none" : props.active ? "purple" : "blue"}; `;
Теперь тернарная операция усложняется. Что, если мы добавим еще одно состояние в наши поля ввода текста позже? Или что, если мы хотим дать каждому состоянию дополнительные стили, кроме цвета? Можете ли вы представить, как можно втиснуть стили в тернарную операцию? Помощник css
пригодится.
const StyledTextField = styled.input` width: 100%; height: 40px; ${(props) => (props.empty && css` color: none; backgroundcolor: white; `) || (props.active && css` color: black; backgroundcolor: whitesmoke; `)} `;
Что мы сделали, так это расширили наш тернарный синтаксис, чтобы вместить больше стилей и с более понятным и организованным синтаксисом. Если предыдущее утверждение кажется неправильным, это потому, что код пытается сделать слишком много. Итак, давайте отступим и уточним:
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; `} `;
Наша доработка разделяет стиль на три разных управляемых и простых для понимания фрагмента. Это победа.
StyleSheetManager
Как и помощник CSS, StyleSheetManager
— это вспомогательный метод для изменения способа обработки стилей. Он принимает определенные реквизиты, такие как disableVendorPrefixes
(вы можете просмотреть полный список), которые помогают вам отказаться от префиксов поставщиков из его поддерева.
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
передается как <StyleSheetManager>
. Таким образом, стилизованные компоненты, обернутые <StyleSheetManager>
, будут отключены, но не те, что находятся в <StyledNav>
.
Упрощенная отладка
Когда я представлял стилизованные компоненты одному из моих коллег, одна из их жалоб заключалась в том, что сложно найти визуализированный элемент в DOM — или, если уж на то пошло, в инструментах разработчика React. Это один из недостатков стилизованных компонентов: пытаясь предоставить уникальные имена классов, он присваивает уникальные хэши элементам, которые оказываются загадочными, но делает displayName
читаемым для облегчения отладки.
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> ); }
По умолчанию стилизованные компоненты отображают LoginButton
как <button class="LoginButton-xxxx xxxx">Login</button>
в DOM и как LoginButton
в инструментах разработчика React, что упрощает отладку. Мы можем переключить логическое значение displayName
, если мы не хотим такого поведения. Для этого требуется конфигурация Babel.
Примечание : В документации указан пакет babel-plugin-styled-components
, а также конфигурационный файл .babelrc
. Проблема в том, что, поскольку мы используем create-react-app
, мы не можем настроить многие вещи, если не извлекаем. Здесь на помощь приходят макросы Babel.
Нам нужно установить babel-plugin-macros
с помощью npm или Yarn, а затем создать babel-plugin-macros.config.js
в корне нашего приложения с содержимым:
module.exports = { styledComponents: { displayName: true, fileName: false, }, };
При инвертированном значении fileName
displayName
будет иметь префикс имени файла для еще большей точности.
Нам также теперь нужно импортировать из macro
:
// Before import styled from "styled-components"; // After import styled from "styled-components/macro";
Заключение
Теперь, когда вы можете программно создавать свой CSS, не злоупотребляйте свободой. Что бы это ни стоило, сделайте все возможное, чтобы сохранить здравомыслие в ваших стилизованных компонентах. Не пытайтесь составлять тяжелые условные операторы и не думайте, что каждая вещь должна быть стилизованным компонентом. Кроме того, не следует чрезмерно абстрагироваться, создавая зарождающиеся стилизованные компоненты для вариантов использования, которые, как вы только предполагаете, где-то за углом.
Дополнительные ресурсы
- Документация, стилизованные компоненты
- «Создание повторно используемой системы компонентов с помощью React.js и styled-components», Лукас Гисдер-Дубе.
- Использование с Next.js
- Использование с Гэтсби