Изоляция стилей — это методы предотвращения конфликтов CSS между компонентами:
/* CSS Modules */
.button { /* становится .button_abc123 */ }
/* Styled Components */
const Button = styled.button`color: blue;`;
/* BEM */
.block__element--modifier { }Изоляция стилей — это как строительство домов с отдельными квартирами. Каждый компонент живёт в своём пространстве и не мешает соседям! 🏠
/* Проблема: глобальные стили конфликтуют */
.button {
background: red;
padding: 10px;
}
/* В другом файле */
.button {
background: blue; /* Перезаписывает предыдущий! */
margin: 5px;
}
/* Результат: непредсказуемые стили */CSS Modules автоматически генерируют уникальные имена классов:
/* Button.module.css */
.button {
background: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.primary {
background: #28a745;
}
.large {
padding: 16px 32px;
font-size: 18px;
}// Button.jsx
import styles from './Button.module.css';
function Button({ children, primary, large }) {
const className = [
styles.button,
primary && styles.primary,
large && styles.large
].filter(Boolean).join(' ');
return (
<button className={className}>
{children}
</button>
);
}
// Результат в DOM:
// <button class="Button_button__2Fk1a Button_primary__3Gh2b">
// Кнопка
// </button>/* Button.module.css */
.base {
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
}
.primary {
composes: base;
background: #007bff;
color: white;
}
.secondary {
composes: base;
background: #6c757d;
color: white;
}
.outline {
composes: base;
background: transparent;
border: 2px solid #007bff;
color: #007bff;
}Styled Components создают изолированные компоненты со стилями:
import styled, { css } from 'styled-components';
// Базовая кнопка
const Button = styled.button`
background: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
transition: all 0.2s ease;
&:hover {
background: #0056b3;
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
`;
// Варианты через props
const StyledButton = styled.button`
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
transition: all 0.2s ease;
${props => props.variant === 'primary' && css`
background: #007bff;
color: white;
&:hover {
background: #0056b3;
}
`}
${props => props.variant === 'secondary' && css`
background: #6c757d;
color: white;
&:hover {
background: #545b62;
}
`}
${props => props.variant === 'outline' && css`
background: transparent;
border: 2px solid #007bff;
color: #007bff;
&:hover {
background: #007bff;
color: white;
}
`}
${props => props.size === 'large' && css`
padding: 16px 32px;
font-size: 18px;
`}
${props => props.size === 'small' && css`
padding: 8px 16px;
font-size: 14px;
`}
${props => props.disabled && css`
opacity: 0.6;
cursor: not-allowed;
&:hover {
background: ${props.variant === 'primary' ? '#007bff' : '#6c757d'};
transform: none;
}
`}
`;
// Использование
function App() {
return (
<div>
<StyledButton variant="primary" size="large">
Основная кнопка
</StyledButton>
<StyledButton variant="outline" size="small">
Контурная кнопка
</StyledButton>
<StyledButton variant="secondary" disabled>
Отключённая кнопка
</StyledButton>
</div>
);
}import styled, { ThemeProvider } from 'styled-components';
// Тема
const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
success: '#28a745',
danger: '#dc3545',
warning: '#ffc107',
info: '#17a2b8'
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px'
},
borderRadius: {
sm: '2px',
md: '4px',
lg: '8px',
xl: '12px'
}
};
// Компонент с темой
const ThemedButton = styled.button`
background: ${props => props.theme.colors[props.color] || props.theme.colors.primary};
color: white;
padding: ${props => props.theme.spacing.md} ${props => props.theme.spacing.lg};
border: none;
border-radius: ${props => props.theme.borderRadius.md};
cursor: pointer;
&:hover {
opacity: 0.9;
}
`;
// Использование с темой
function App() {
return (
<ThemeProvider theme={theme}>
<ThemedButton color="primary">Основная</ThemedButton>
<ThemedButton color="success">Успех</ThemedButton>
<ThemedButton color="danger">Опасность</ThemedButton>
</ThemeProvider>
);
}Shadow DOM обеспечивает полную изоляцию стилей на уровне браузера:
// Создание Web Component с Shadow DOM
class IsolatedButton extends HTMLElement {
constructor() {
super();
// Создаём Shadow DOM
this.attachShadow({ mode: 'open' });
// Стили полностью изолированы
this.shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
}
button {
background: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
transition: all 0.2s ease;
}
button:hover {
background: #0056b3;
transform: translateY(-1px);
}
button:active {
transform: translateY(0);
}
/* Эти стили НЕ влияют на внешние элементы */
.global-class {
color: red;
}
</style>
<button>
<slot></slot>
</button>
`;
}
}
// Регистрируем компонент
customElements.define('isolated-button', IsolatedButton);
// Использование
// <isolated-button>Изолированная кнопка</isolated-button>import { useEffect, useRef } from 'react';
import { createRoot } from 'react-dom/client';
function ShadowWrapper({ children, styles }) {
const shadowRef = useRef();
const rootRef = useRef();
useEffect(() => {
const shadowRoot = shadowRef.current.attachShadow({ mode: 'open' });
// Добавляем стили в Shadow DOM
const styleElement = document.createElement('style');
styleElement.textContent = styles;
shadowRoot.appendChild(styleElement);
// Создаём контейнер для React
const container = document.createElement('div');
shadowRoot.appendChild(container);
// Рендерим React компонент в Shadow DOM
rootRef.current = createRoot(container);
rootRef.current.render(children);
return () => {
rootRef.current?.unmount();
};
}, [children, styles]);
return <div ref={shadowRef}></div>;
}
// Использование
function App() {
const isolatedStyles = `
.button {
background: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button:hover {
background: #0056b3;
}
`;
return (
<ShadowWrapper styles={isolatedStyles}>
<button className="button">
Изолированная кнопка
</button>
</ShadowWrapper>
);
}/** @jsxImportSource @emotion/react */
import { css, jsx } from '@emotion/react';
import styled from '@emotion/styled';
// CSS prop
const buttonStyle = css`
background: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
background: #0056b3;
}
`;
function Button() {
return (
<button css={buttonStyle}>
Emotion кнопка
</button>
);
}
// Styled компонент
const StyledButton = styled.button`
background: ${props => props.primary ? '#007bff' : '#6c757d'};
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
opacity: 0.9;
}
`;import { createUseStyles } from 'react-jss';
const useStyles = createUseStyles({
button: {
background: '#007bff',
color: 'white',
padding: [12, 24],
border: 'none',
borderRadius: 4,
cursor: 'pointer',
transition: 'all 0.2s ease',
'&:hover': {
background: '#0056b3',
transform: 'translateY(-1px)'
},
'&:active': {
transform: 'translateY(0)'
}
},
primary: {
extend: 'button',
background: '#007bff'
},
secondary: {
extend: 'button',
background: '#6c757d'
}
});
function Button({ variant = 'primary', children }) {
const classes = useStyles();
return (
<button className={classes[variant]}>
{children}
</button>
);
}import { styled } from '@stitches/react';
const Button = styled('button', {
// Базовые стили
padding: '12px 24px',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontFamily: 'inherit',
transition: 'all 0.2s ease',
// Варианты
variants: {
variant: {
primary: {
background: '#007bff',
color: 'white',
'&:hover': {
background: '#0056b3'
}
},
secondary: {
background: '#6c757d',
color: 'white',
'&:hover': {
background: '#545b62'
}
},
outline: {
background: 'transparent',
border: '2px solid #007bff',
color: '#007bff',
'&:hover': {
background: '#007bff',
color: 'white'
}
}
},
size: {
small: {
padding: '8px 16px',
fontSize: '14px'
},
large: {
padding: '16px 32px',
fontSize: '18px'
}
}
},
// Составные варианты
compoundVariants: [
{
variant: 'primary',
size: 'large',
css: {
fontWeight: 'bold'
}
}
],
// Значения по умолчанию
defaultVariants: {
variant: 'primary',
size: 'medium'
}
});
// Использование
<Button variant="outline" size="large">
Большая контурная кнопка
</Button>/* Блок */
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 20px;
}
/* Элементы */
.card__header {
border-bottom: 1px solid #eee;
padding-bottom: 15px;
margin-bottom: 15px;
}
.card__title {
font-size: 24px;
font-weight: bold;
margin: 0;
}
.card__subtitle {
font-size: 16px;
color: #666;
margin: 5px 0 0 0;
}
.card__content {
line-height: 1.6;
}
.card__footer {
border-top: 1px solid #eee;
padding-top: 15px;
margin-top: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.card__button {
background: #007bff;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* Модификаторы */
.card--featured {
border: 2px solid #007bff;
box-shadow: 0 4px 8px rgba(0,123,255,0.2);
}
.card--compact {
padding: 12px;
}
.card__button--secondary {
background: #6c757d;
}
.card__button--large {
padding: 12px 24px;
font-size: 16px;
}<!-- Использование BEM -->
<div class="card card--featured">
<div class="card__header">
<h2 class="card__title">Заголовок карточки</h2>
<p class="card__subtitle">Подзаголовок</p>
</div>
<div class="card__content">
Содержимое карточки
</div>
<div class="card__footer">
<button class="card__button">Основная</button>
<button class="card__button card__button--secondary">
Вторичная
</button>
</div>
</div>/* Base - базовые стили */
body, h1, h2, p { margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; }
/* Layout - макет */
.l-header { /* layout-header */ }
.l-sidebar { /* layout-sidebar */ }
.l-content { /* layout-content */ }
/* Module - модули */
.button { }
.card { }
.navigation { }
/* State - состояния */
.is-hidden { display: none; }
.is-active { background: #007bff; }
.is-disabled { opacity: 0.5; }
/* Theme - темы */
.theme-dark .button { background: #333; }
.theme-light .button { background: #fff; }Vue.js предоставляет встроенную изоляцию стилей:
<template>
<div class="card">
<h2 class="title">{{ title }}</h2>
<p class="content">{{ content }}</p>
<button class="button" @click="handleClick">
{{ buttonText }}
</button>
</div>
</template>
<script>
export default {
props: ['title', 'content', 'buttonText'],
methods: {
handleClick() {
this.$emit('click');
}
}
}
</script>
<style scoped>
/* Эти стили применяются только к этому компоненту */
.card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.title {
font-size: 24px;
margin-bottom: 10px;
color: #333;
}
.content {
line-height: 1.6;
margin-bottom: 20px;
color: #666;
}
.button {
background: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s ease;
}
.button:hover {
background: #0056b3;
}
/* Глубокие селекторы для дочерних компонентов */
.card :deep(.child-component) {
margin-top: 10px;
}
/* Глобальные стили */
:global(.global-class) {
font-weight: bold;
}
</style>// postcss.config.js
module.exports = {
plugins: [
require('postcss-modules')({
generateScopedName: '[name]__[local]___[hash:base64:5]'
})
]
};// Автоматически добавляет префикс ко всем селекторам
module.exports = {
plugins: [
require('postcss-prefixwrap')('.my-component')
]
};
// Входной CSS:
// .button { color: red; }
// Выходной CSS:
// .my-component .button { color: red; }| Метод | Изоляция | Производительность | Сложность | DX | SSR |
|---|---|---|---|---|---|
| CSS Modules | ✅ Высокая | ✅ Отличная | 🟡 Средняя | ✅ Хорошая | ✅ Да |
| Styled Components | ✅ Высокая | 🟡 Средняя | 🟡 Средняя | ✅ Отличная | ✅ Да |
| Shadow DOM | ✅ Полная | ✅ Отличная | 🔴 Высокая | 🟡 Средняя | ❌ Нет |
| Emotion | ✅ Высокая | 🟡 Средняя | 🟡 Средняя | ✅ Отличная | ✅ Да |
| BEM | 🟡 Условная | ✅ Отличная | 🟡 Средняя | 🟡 Средняя | ✅ Да |
| CSS Scoped | ✅ Высокая | ✅ Отличная | ✅ Низкая | ✅ Отличная | ✅ Да |
// Для React приложений
const recommendations = {
'small-project': 'CSS Modules или Styled Components',
'medium-project': 'Styled Components + Theme Provider',
'large-project': 'CSS Modules + PostCSS или Stitches',
'component-library': 'Styled Components или Stitches',
'legacy-project': 'BEM + PostCSS плагины'
};
// Для Vue.js
const vueRecommendations = {
'any-size': 'CSS Scoped (встроенный)'
};
// Для Web Components
const webComponentsRecommendations = {
'any-size': 'Shadow DOM (нативный)'
};// Комбинирование методов для максимальной эффективности
// 1. CSS Modules для компонентов + глобальные стили
import styles from './Button.module.css';
import './global.css'; // Глобальные стили
// 2. Styled Components для динамических стилей + CSS для статичных
const DynamicButton = styled.button`
background: ${props => props.theme.colors[props.variant]};
`;
// 3. BEM для больших блоков + CSS Modules для компонентов
<div className="page-layout"> {/* BEM */}
<Button className={styles.button} /> {/* CSS Modules */}
</div>// ✅ Хорошо: один подход в проекте
// Везде CSS Modules
import buttonStyles from './Button.module.css';
import cardStyles from './Card.module.css';
// ❌ Плохо: смешивание подходов без системы
import buttonStyles from './Button.module.css';
import styled from 'styled-components';
const Card = styled.div`...`;// ✅ Хорошо: общие стили в переменных
const theme = {
colors: { primary: '#007bff' },
spacing: { md: '16px' }
};
// ❌ Плохо: дублирование значений
const Button1 = styled.button`background: #007bff;`;
const Button2 = styled.button`background: #007bff;`; // Дублирование!// ✅ Хорошо: статические стили
const StaticButton = styled.button`
padding: 12px 24px;
border-radius: 4px;
`;
// ❌ Плохо: динамические стили без мемоизации
const DynamicButton = styled.button`
padding: ${() => Math.random() * 20}px; // Пересчёт каждый рендер!
`;Правильная изоляция стилей сделает ваш код предсказуемым и легко поддерживаемым! 🚀
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪