React + Styled Components: стилизация
2025-08-07

React + Styled Components: стилизация

Styled Components — это библиотека для CSS-in-JS, которая позволяет писать CSS код прямо в JavaScript файлах. Она предоставляет мощный и элегантный способ стилизации React компонентов, объединяя преимущества CSS и JavaScript. Styled Components позволяет создавать переиспользуемые, тематизируемые компоненты с динамическими стилями, что делает разработку более эффективной и поддерживаемой.

1. Что такое Styled Components?

Основные концепции

Styled Components — это библиотека, которая позволяет:

  • Писать CSS в JavaScript — стили определяются как компоненты
  • Динамические стили — стили могут изменяться на основе пропсов
  • Темизация — глобальные темы для всего приложения
  • Автоматическая генерация уникальных классов — избежание конфликтов имён
  • Лучшая производительность — только используемые стили попадают в бандл

Преимущества CSS-in-JS

  • Компонентный подход — стили привязаны к компонентам
  • Динамические стили — стили могут зависеть от состояния
  • Типобезопасность — поддержка TypeScript
  • Лучшая изоляция — стили не влияют на другие компоненты
  • Переиспользование — компоненты можно легко переиспользовать

2. Установка и настройка

Установка Styled Components

npm install styled-components

Или с использованием yarn:

yarn add styled-components

Установка типов для TypeScript

npm install --save-dev @types/styled-components

Базовая настройка

import styled from 'styled-components';

// Простой styled компонент
const Button = styled.button`
  background-color: #007bff;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    background-color: #0056b3;
  }
`;

function App() {
  return (
    <div>
      <Button>Нажми меня</Button>
    </div>
  );
}

3. Создание базовых компонентов

Стилизация HTML элементов

import styled from 'styled-components';

// Стилизация div
const Container = styled.div`
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;
`;

// Стилизация заголовка
const Title = styled.h1`
  font-size: 2.5rem;
  color: #333;
  margin-bottom: 1rem;
  text-align: center;
`;

// Стилизация параграфа
const Paragraph = styled.p`
  font-size: 1.1rem;
  line-height: 1.6;
  color: #666;
  margin-bottom: 1rem;
`;

// Стилизация кнопки
const Button = styled.button`
  background-color: #007bff;
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 6px;
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.3s ease;

  &:hover {
    background-color: #0056b3;
  }

  &:active {
    transform: translateY(1px);
  }
`;

function Card() {
  return (
    <Container>
      <Title>Заголовок карточки</Title>
      <Paragraph>
        Это пример использования Styled Components для создания
        красивых и переиспользуемых компонентов.
      </Paragraph>
      <Button>Подробнее</Button>
    </Container>
  );
}

Стилизация с псевдоклассами

import styled from 'styled-components';

const Link = styled.a`
  color: #007bff;
  text-decoration: none;
  font-weight: 500;

  &:hover {
    color: #0056b3;
    text-decoration: underline;
  }

  &:visited {
    color: #6f42c1;
  }

  &:active {
    color: #e83e8c;
  }
`;

const Input = styled.input`
  width: 100%;
  padding: 12px;
  border: 2px solid #ddd;
  border-radius: 4px;
  font-size: 1rem;
  transition: border-color 0.3s ease;

  &:focus {
    outline: none;
    border-color: #007bff;
    box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
  }

  &:invalid {
    border-color: #dc3545;
  }
`;

const List = styled.ul`
  list-style: none;
  padding: 0;

  li {
    padding: 8px 0;
    border-bottom: 1px solid #eee;

    &:last-child {
      border-bottom: none;
    }

    &:hover {
      background-color: #f8f9fa;
    }
  }
`;

4. Динамические стили с пропсами

Условные стили

import styled from 'styled-components';

const Button = styled.button`
  background-color: ${props => props.primary ? '#007bff' : '#6c757d'};
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 6px;
  font-size: 1rem;
  cursor: pointer;
  transition: all 0.3s ease;

  &:hover {
    background-color: ${props => props.primary ? '#0056b3' : '#545b62'};
  }

  ${props => props.large && `
    padding: 16px 32px;
    font-size: 1.2rem;
  `}

  ${props => props.disabled && `
    opacity: 0.6;
    cursor: not-allowed;

    &:hover {
      background-color: ${props.primary ? '#007bff' : '#6c757d'};
    }
  `}
`;

function App() {
  return (
    <div>
      <Button primary>Основная кнопка</Button>
      <Button>Вторичная кнопка</Button>
      <Button primary large>Большая основная кнопка</Button>
      <Button disabled>Отключенная кнопка</Button>
    </div>
  );
}

Стили на основе состояния

import React, { useState } from 'react';
import styled from 'styled-components';

const ToggleButton = styled.button`
  background-color: ${props => props.isActive ? '#28a745' : '#dc3545'};
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s ease;

  &:hover {
    background-color: ${props => props.isActive ? '#218838' : '#c82333'};
  }
`;

const ProgressBar = styled.div`
  width: 100%;
  height: 20px;
  background-color: #e9ecef;
  border-radius: 10px;
  overflow: hidden;

  &::after {
    content: '';
    display: block;
    height: 100%;
    background-color: #007bff;
    width: ${props => props.progress}%;
    transition: width 0.3s ease;
  }
`;

function ToggleComponent() {
  const [isActive, setIsActive] = useState(false);
  const [progress, setProgress] = useState(0);

  return (
    <div>
      <ToggleButton 
        isActive={isActive} 
        onClick={() => setIsActive(!isActive)}
      >
        {isActive ? 'Активно' : 'Неактивно'}
      </ToggleButton>

      <ProgressBar progress={progress} />
      <button onClick={() => setProgress(Math.min(100, progress + 10))}>
        Увеличить прогресс
      </button>
    </div>
  );
}

5. Расширение и композиция компонентов

Расширение существующих компонентов

import styled from 'styled-components';

const Button = styled.button`
  background-color: #007bff;
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 6px;
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.3s ease;

  &:hover {
    background-color: #0056b3;
  }
`;

// Расширение базовой кнопки
const SuccessButton = styled(Button)`
  background-color: #28a745;

  &:hover {
    background-color: #218838;
  }
`;

const DangerButton = styled(Button)`
  background-color: #dc3545;

  &:hover {
    background-color: #c82333;
  }
`;

const OutlineButton = styled(Button)`
  background-color: transparent;
  color: #007bff;
  border: 2px solid #007bff;

  &:hover {
    background-color: #007bff;
    color: white;
  }
`;

function App() {
  return (
    <div>
      <Button>Обычная кнопка</Button>
      <SuccessButton>Успех</SuccessButton>
      <DangerButton>Ошибка</DangerButton>
      <OutlineButton>Контурная кнопка</OutlineButton>
    </div>
  );
}

Композиция компонентов

import styled from 'styled-components';

const Card = styled.div`
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  padding: 20px;
  margin: 10px;
`;

const CardHeader = styled.div`
  border-bottom: 1px solid #eee;
  padding-bottom: 15px;
  margin-bottom: 15px;
`;

const CardTitle = styled.h3`
  margin: 0;
  color: #333;
  font-size: 1.5rem;
`;

const CardBody = styled.div`
  color: #666;
  line-height: 1.6;
`;

const CardFooter = styled.div`
  border-top: 1px solid #eee;
  padding-top: 15px;
  margin-top: 15px;
  display: flex;
  justify-content: space-between;
  align-items: center;
`;

const CardButton = styled.button`
  background-color: #007bff;
  color: white;
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    background-color: #0056b3;
  }
`;

function ProductCard({ title, description, price, onBuy }) {
  return (
    <Card>
      <CardHeader>
        <CardTitle>{title}</CardTitle>
      </CardHeader>
      <CardBody>
        {description}
      </CardBody>
      <CardFooter>
        <span>Цена: ${price}</span>
        <CardButton onClick={onBuy}>Купить</CardButton>
      </CardFooter>
    </Card>
  );
}

6. Темизация с ThemeProvider

Создание темы

import styled, { ThemeProvider } from 'styled-components';

// Определение темы
const lightTheme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    success: '#28a745',
    danger: '#dc3545',
    warning: '#ffc107',
    info: '#17a2b8',
    light: '#f8f9fa',
    dark: '#343a40',
    white: '#ffffff',
    body: '#ffffff',
    text: '#333333',
    textMuted: '#666666'
  },
  fonts: {
    primary: 'Arial, sans-serif',
    secondary: 'Georgia, serif'
  },
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px'
  },
  borderRadius: {
    sm: '4px',
    md: '8px',
    lg: '12px'
  },
  shadows: {
    sm: '0 2px 4px rgba(0, 0, 0, 0.1)',
    md: '0 4px 8px rgba(0, 0, 0, 0.1)',
    lg: '0 8px 16px rgba(0, 0, 0, 0.1)'
  }
};

const darkTheme = {
  ...lightTheme,
  colors: {
    ...lightTheme.colors,
    body: '#1a1a1a',
    text: '#ffffff',
    textMuted: '#cccccc'
  }
};

// Компоненты с использованием темы
const Container = styled.div`
  background-color: ${props => props.theme.colors.body};
  color: ${props => props.theme.colors.text};
  font-family: ${props => props.theme.fonts.primary};
  padding: ${props => props.theme.spacing.lg};
  min-height: 100vh;
`;

const Button = styled.button`
  background-color: ${props => props.theme.colors.primary};
  color: ${props => props.theme.colors.white};
  padding: ${props => props.theme.spacing.sm} ${props => props.theme.spacing.md};
  border: none;
  border-radius: ${props => props.theme.borderRadius.sm};
  font-size: 1rem;
  cursor: pointer;
  transition: background-color 0.3s ease;

  &:hover {
    background-color: ${props => props.theme.colors.primary}dd;
  }
`;

const Card = styled.div`
  background-color: ${props => props.theme.colors.white};
  border-radius: ${props => props.theme.borderRadius.md};
  box-shadow: ${props => props.theme.shadows.md};
  padding: ${props => props.theme.spacing.lg};
  margin: ${props => props.theme.spacing.md} 0;
`;

function App() {
  const [isDark, setIsDark] = useState(false);

  return (
    <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
      <Container>
        <Button onClick={() => setIsDark(!isDark)}>
          Переключить тему
        </Button>
        <Card>
          <h2>Пример карточки</h2>
          <p>Этот компонент автоматически адаптируется к выбранной теме.</p>
        </Card>
      </Container>
    </ThemeProvider>
  );
}

7. Анимации и переходы

CSS анимации

import styled, { keyframes } from 'styled-components';

// Определение анимации
const fadeIn = keyframes`
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
`;

const pulse = keyframes`
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.05);
  }
  100% {
    transform: scale(1);
  }
`;

const rotate = keyframes`
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
`;

// Компоненты с анимациями
const AnimatedCard = styled.div`
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  padding: 20px;
  margin: 10px;
  animation: ${fadeIn} 0.5s ease-out;

  &:hover {
    animation: ${pulse} 0.3s ease-in-out;
  }
`;

const LoadingSpinner = styled.div`
  width: 40px;
  height: 40px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #007bff;
  border-radius: 50%;
  animation: ${rotate} 1s linear infinite;
`;

const SlideInButton = styled.button`
  background-color: #007bff;
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transform: translateX(-100%);
  animation: ${fadeIn} 0.5s ease-out 0.5s forwards;

  &:hover {
    background-color: #0056b3;
  }
`;

function AnimatedComponents() {
  return (
    <div>
      <AnimatedCard>
        <h3>Анимированная карточка</h3>
        <p>Эта карточка появляется с анимацией fade-in</p>
      </AnimatedCard>

      <LoadingSpinner />

      <SlideInButton>
        Кнопка с задержкой
      </SlideInButton>
    </div>
  );
}

Переходы и hover эффекты

import styled from 'styled-components';

const HoverCard = styled.div`
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  padding: 20px;
  margin: 10px;
  transition: all 0.3s ease;

  &:hover {
    transform: translateY(-5px);
    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
  }
`;

const GradientButton = styled.button`
  background: linear-gradient(45deg, #007bff, #0056b3);
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.3s ease;

  &:hover {
    background: linear-gradient(45deg, #0056b3, #004085);
    transform: scale(1.05);
  }

  &:active {
    transform: scale(0.95);
  }
`;

const ImageCard = styled.div`
  position: relative;
  overflow: hidden;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

  img {
    width: 100%;
    height: 200px;
    object-fit: cover;
    transition: transform 0.3s ease;
  }

  &:hover img {
    transform: scale(1.1);
  }

  &::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
    opacity: 0;
    transition: opacity 0.3s ease;
  }

  &:hover::after {
    opacity: 1;
  }
`;

8. Медиа-запросы и адаптивность

Адаптивные компоненты

import styled from 'styled-components';

const ResponsiveContainer = styled.div`
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;

  @media (max-width: 768px) {
    padding: 0 15px;
  }

  @media (max-width: 480px) {
    padding: 0 10px;
  }
`;

const ResponsiveGrid = styled.div`
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;

  @media (max-width: 1024px) {
    grid-template-columns: repeat(3, 1fr);
    gap: 15px;
  }

  @media (max-width: 768px) {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }

  @media (max-width: 480px) {
    grid-template-columns: 1fr;
    gap: 10px;
  }
`;

const ResponsiveText = styled.h1`
  font-size: 3rem;
  margin-bottom: 1rem;

  @media (max-width: 768px) {
    font-size: 2rem;
  }

  @media (max-width: 480px) {
    font-size: 1.5rem;
  }
`;

const ResponsiveButton = styled.button`
  background-color: #007bff;
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 6px;
  font-size: 1rem;
  cursor: pointer;

  @media (max-width: 768px) {
    padding: 10px 20px;
    font-size: 0.9rem;
  }

  @media (max-width: 480px) {
    padding: 8px 16px;
    font-size: 0.8rem;
  }
`;

function ResponsiveLayout() {
  return (
    <ResponsiveContainer>
      <ResponsiveText>Адаптивный заголовок</ResponsiveText>

      <ResponsiveGrid>
        <div>Элемент 1</div>
        <div>Элемент 2</div>
        <div>Элемент 3</div>
        <div>Элемент 4</div>
      </ResponsiveGrid>

      <ResponsiveButton>Адаптивная кнопка</ResponsiveButton>
    </ResponsiveContainer>
  );
}

9. TypeScript с Styled Components

Типизированные компоненты

import styled from 'styled-components';

interface ButtonProps {
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
}

const StyledButton = styled.button<ButtonProps>`
  background-color: ${props => props.primary ? '#007bff' : '#6c757d'};
  color: white;
  border: none;
  border-radius: 6px;
  cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
  opacity: ${props => props.disabled ? 0.6 : 1};

  ${props => {
    switch (props.size) {
      case 'small':
        return `
          padding: 8px 16px;
          font-size: 0.875rem;
        `;
      case 'large':
        return `
          padding: 16px 32px;
          font-size: 1.125rem;
        `;
      default:
        return `
          padding: 12px 24px;
          font-size: 1rem;
        `;
    }
  }}

  &:hover {
    background-color: ${props => 
      props.disabled 
        ? (props.primary ? '#007bff' : '#6c757d')
        : (props.primary ? '#0056b3' : '#545b62')
    };
  }
`;

interface CardProps {
  elevation?: 'low' | 'medium' | 'high';
}

const StyledCard = styled.div<CardProps>`
  background: white;
  border-radius: 8px;
  padding: 20px;
  margin: 10px;

  ${props => {
    switch (props.elevation) {
      case 'low':
        return 'box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);';
      case 'high':
        return 'box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);';
      default:
        return 'box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);';
    }
  }}
`;

function TypedComponents() {
  return (
    <div>
      <StyledButton primary size="large">
        Большая основная кнопка
      </StyledButton>

      <StyledButton size="small" disabled>
        Маленькая отключенная кнопка
      </StyledButton>

      <StyledCard elevation="high">
        Карточка с высокой тенью
      </StyledCard>
    </div>
  );
}

10. Лучшие практики

Организация кода

// styles/theme.js
export const theme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    // ... другие цвета
  },
  // ... другие настройки темы
};

// styles/GlobalStyles.js
import { createGlobalStyle } from 'styled-components';

export const GlobalStyles = createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }

  body {
    font-family: 'Arial', sans-serif;
    line-height: 1.6;
    color: #333;
  }

  button {
    cursor: pointer;
  }

  a {
    text-decoration: none;
    color: inherit;
  }
`;

// components/Button/styles.js
import styled from 'styled-components';

export const StyledButton = styled.button`
  // стили кнопки
`;

// components/Button/index.js
export { default } from './Button';
export { StyledButton } from './styles';

// components/Button/Button.js
import React from 'react';
import { StyledButton } from './styles';

const Button = ({ children, ...props }) => {
  return <StyledButton {...props}>{children}</StyledButton>;
};

export default Button;

Производительность

import styled from 'styled-components';

// Хорошо: статические стили
const GoodButton = styled.button`
  background-color: #007bff;
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 6px;
`;

// Плохо: динамические стили в каждом рендере
const BadButton = styled.button`
  background-color: ${props => props.theme.colors.primary};
  color: ${props => props.theme.colors.white};
  padding: ${props => props.theme.spacing.md};
`;

// Хорошо: использование CSS переменных
const BetterButton = styled.button`
  background-color: var(--primary-color);
  color: var(--white-color);
  padding: var(--spacing-md);
`;

// Хорошо: мемоизация для сложных вычислений
const ComplexButton = styled.button`
  background: ${props => {
    // Кэшируем результат для производительности
    if (props.cachedBackground) return props.cachedBackground;

    const background = `linear-gradient(45deg, ${props.theme.colors.primary}, ${props.theme.colors.secondary})`;
    props.cachedBackground = background;
    return background;
  }};
`;

Переиспользование стилей

import styled, { css } from 'styled-components';

// Базовые стили
const baseButtonStyles = css`
  padding: 12px 24px;
  border: none;
  border-radius: 6px;
  font-size: 1rem;
  cursor: pointer;
  transition: all 0.3s ease;
`;

const primaryButtonStyles = css`
  background-color: #007bff;
  color: white;

  &:hover {
    background-color: #0056b3;
  }
`;

const secondaryButtonStyles = css`
  background-color: #6c757d;
  color: white;

  &:hover {
    background-color: #545b62;
  }
`;

// Компоненты с переиспользованием стилей
const PrimaryButton = styled.button`
  ${baseButtonStyles}
  ${primaryButtonStyles}
`;

const SecondaryButton = styled.button`
  ${baseButtonStyles}
  ${secondaryButtonStyles}
`;

// Миксины для повторяющихся стилей
const flexCenter = css`
  display: flex;
  align-items: center;
  justify-content: center;
`;

const cardShadow = css`
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
`;

const Card = styled.div`
  ${flexCenter}
  ${cardShadow}
  background: white;
  border-radius: 8px;
  padding: 20px;
`;

Заключение

Styled Components предоставляет мощный и элегантный способ стилизации React приложений. Основные преимущества:

  • Компонентный подход — стили привязаны к компонентам
  • Динамические стили — стили могут зависеть от пропсов и состояния
  • Темизация — глобальные темы для всего приложения
  • TypeScript поддержка — полная типизация
  • Производительность — только используемые стили попадают в бандл
  • Лучшая изоляция — избежание конфликтов имён

При правильном использовании Styled Components может значительно улучшить качество и поддерживаемость кода, делая разработку более эффективной и приятной.