CSS-in-JS — подход, при котором стили пишутся прямо в JavaScript-файлах, часто с помощью тегированных шаблонных литералов. Это дало разработчикам возможность использовать всю мощь JavaScript внутри CSS: условия, циклы, переменные, типизацию.
Проблема обычного CSS в масштабе:
CSS-in-JS решает это: стили привязаны к компоненту, неиспользуемые стили удаляются автоматически.
import styled from 'styled-components'
// Создаём styled-компонент — возвращает React-компонент
const Button = styled.button`
background: ${props => props.primary ? '#7b2ff7' : 'white'};
color: ${props => props.primary ? 'white' : '#7b2ff7'};
border: 2px solid #7b2ff7;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
&:hover {
opacity: 0.9;
transform: translateY(-1px);
}
`
// Использование
<Button>Обычная кнопка</Button>
<Button primary>Основная кнопка</Button>Под капотом: генерирует уникальный хеш класса, инжектирует <style> в <head>.
import { css, cx } from '@emotion/css'
const buttonStyle = css`
background: #7b2ff7;
color: white;
padding: 8px 16px;
`
// jsx-метод
import { jsx } from '@emotion/react'
const button = <button css={{ background: '#7b2ff7', padding: '8px 16px' }}>Click</button>Emotion легче styled-components, поддерживает object syntax и string syntax.
/* Button.module.css */
.button { background: #7b2ff7; color: white; }
.button--primary { font-weight: bold; }import styles from './Button.module.css'
<button className={styles.button}>Click</button>
// Рендерится как: <button class="Button_button__xK2sA">CSS Modules: локальные имена через хеш, но стили остаются в CSS-файлах. Нулевой runtime.
| Подход | Runtime | Bundle size | SSR | DX |
|----------------------|---------|-------------|-----|------|
| styled-components | Да | +30KB | Да | ⭐⭐⭐ |
| Emotion | Да | +12KB | Да | ⭐⭐⭐ |
| CSS Modules | Нет | +0KB | Да | ⭐⭐ |
| Vanilla Extract | Нет | +0KB | Да | ⭐⭐⭐ |
| Tailwind | Нет | +0KB | Да | ⭐⭐⭐ |
Zero-runtime (Vanilla Extract, Linaria): компилируются в статический CSS во время сборки.
CSS-in-JS автоматически решает проблему критического CSS — стили добавляются только для компонентов, которые есть на странице:
// На сервере styled-components собирает только нужные стили
import { ServerStyleSheet } from 'styled-components'
const sheet = new ServerStyleSheet()
const html = renderToString(sheet.collectStyles(<App />))
const styleTags = sheet.getStyleTags()
// → <style data-styled="...">только использованные стили</style>// Emotion / Vanilla Extract
const styles = css({
display: 'flex',
gap: '16px',
'@media (max-width: 768px)': {
flexDirection: 'column',
},
':hover': {
opacity: 0.8,
},
})Минимальная реализация CSS-in-JS: тегированный шаблонный литерал, генерация уникальных классов и инъекция стилей
// Мини CSS-in-JS система — как работает styled-components внутри
let classCounter = 0
const injectedStyles = new Map()
function generateClassName() {
return `sc-${(++classCounter).toString(36)}`
}
function injectStyle(className, cssText) {
if (!injectedStyles.has(className)) {
injectedStyles.set(className, cssText)
// В браузере: инжектируем в <style>
let styleTag = document.getElementById('css-in-js-styles')
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.id = 'css-in-js-styles'
document.head.appendChild(styleTag)
}
styleTag.textContent += `.${className} { ${cssText} }\n`
}
}
// Tagged template literal функция
function css(strings, ...values) {
const cssText = strings.reduce((acc, str, i) => {
return acc + str + (values[i] !== undefined ? values[i] : '')
}, '').trim()
const className = generateClassName()
injectStyle(className, cssText)
return className
}
// Создаём "компоненты" через factory
function createStyledEl(tag) {
return function(strings, ...values) {
const cssText = strings.reduce((acc, str, i) => {
return acc + str + (values[i] !== undefined ? values[i] : '')
}, '').trim()
return function(props = {}) {
// Вычисляем финальный CSS с учётом props (упрощённо)
const el = document.createElement(tag)
const className = generateClassName()
injectStyle(className, cssText)
el.className = className
if (props.textContent) el.textContent = props.textContent
return el
}
}
}
const styled = {
div: createStyledEl('div'),
button: createStyledEl('button'),
span: createStyledEl('span'),
}
// Используем нашу мини-систему
const Card = styled.div`
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
font-family: sans-serif;
margin: 8px;
`
const PrimaryButton = styled.button`
background: #7b2ff7;
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
`
// Создаём элементы
const card = Card({ textContent: '' })
document.body.appendChild(card)
const title = document.createElement('h3')
title.textContent = 'CSS-in-JS компонент'
title.style.margin = '0 0 8px'
card.appendChild(title)
const btn = PrimaryButton({ textContent: 'Кнопка' })
card.appendChild(btn)
// Выводим результат
console.log('Инжектированные классы:', [...injectedStyles.keys()])
console.log('Количество инжектированных стилей:', injectedStyles.size)
console.log('Card className:', card.className)
console.log('Button className:', btn.className)
// Проверяем что стили инжектированы
const styleTag = document.getElementById('css-in-js-styles')
console.log('Style tag содержит правил:', styleTag.textContent.split('\n').filter(l => l.trim()).length)CSS-in-JS — подход, при котором стили пишутся прямо в JavaScript-файлах, часто с помощью тегированных шаблонных литералов. Это дало разработчикам возможность использовать всю мощь JavaScript внутри CSS: условия, циклы, переменные, типизацию.
Проблема обычного CSS в масштабе:
CSS-in-JS решает это: стили привязаны к компоненту, неиспользуемые стили удаляются автоматически.
import styled from 'styled-components'
// Создаём styled-компонент — возвращает React-компонент
const Button = styled.button`
background: ${props => props.primary ? '#7b2ff7' : 'white'};
color: ${props => props.primary ? 'white' : '#7b2ff7'};
border: 2px solid #7b2ff7;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
&:hover {
opacity: 0.9;
transform: translateY(-1px);
}
`
// Использование
<Button>Обычная кнопка</Button>
<Button primary>Основная кнопка</Button>Под капотом: генерирует уникальный хеш класса, инжектирует <style> в <head>.
import { css, cx } from '@emotion/css'
const buttonStyle = css`
background: #7b2ff7;
color: white;
padding: 8px 16px;
`
// jsx-метод
import { jsx } from '@emotion/react'
const button = <button css={{ background: '#7b2ff7', padding: '8px 16px' }}>Click</button>Emotion легче styled-components, поддерживает object syntax и string syntax.
/* Button.module.css */
.button { background: #7b2ff7; color: white; }
.button--primary { font-weight: bold; }import styles from './Button.module.css'
<button className={styles.button}>Click</button>
// Рендерится как: <button class="Button_button__xK2sA">CSS Modules: локальные имена через хеш, но стили остаются в CSS-файлах. Нулевой runtime.
| Подход | Runtime | Bundle size | SSR | DX |
|----------------------|---------|-------------|-----|------|
| styled-components | Да | +30KB | Да | ⭐⭐⭐ |
| Emotion | Да | +12KB | Да | ⭐⭐⭐ |
| CSS Modules | Нет | +0KB | Да | ⭐⭐ |
| Vanilla Extract | Нет | +0KB | Да | ⭐⭐⭐ |
| Tailwind | Нет | +0KB | Да | ⭐⭐⭐ |
Zero-runtime (Vanilla Extract, Linaria): компилируются в статический CSS во время сборки.
CSS-in-JS автоматически решает проблему критического CSS — стили добавляются только для компонентов, которые есть на странице:
// На сервере styled-components собирает только нужные стили
import { ServerStyleSheet } from 'styled-components'
const sheet = new ServerStyleSheet()
const html = renderToString(sheet.collectStyles(<App />))
const styleTags = sheet.getStyleTags()
// → <style data-styled="...">только использованные стили</style>// Emotion / Vanilla Extract
const styles = css({
display: 'flex',
gap: '16px',
'@media (max-width: 768px)': {
flexDirection: 'column',
},
':hover': {
opacity: 0.8,
},
})Минимальная реализация CSS-in-JS: тегированный шаблонный литерал, генерация уникальных классов и инъекция стилей
// Мини CSS-in-JS система — как работает styled-components внутри
let classCounter = 0
const injectedStyles = new Map()
function generateClassName() {
return `sc-${(++classCounter).toString(36)}`
}
function injectStyle(className, cssText) {
if (!injectedStyles.has(className)) {
injectedStyles.set(className, cssText)
// В браузере: инжектируем в <style>
let styleTag = document.getElementById('css-in-js-styles')
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.id = 'css-in-js-styles'
document.head.appendChild(styleTag)
}
styleTag.textContent += `.${className} { ${cssText} }\n`
}
}
// Tagged template literal функция
function css(strings, ...values) {
const cssText = strings.reduce((acc, str, i) => {
return acc + str + (values[i] !== undefined ? values[i] : '')
}, '').trim()
const className = generateClassName()
injectStyle(className, cssText)
return className
}
// Создаём "компоненты" через factory
function createStyledEl(tag) {
return function(strings, ...values) {
const cssText = strings.reduce((acc, str, i) => {
return acc + str + (values[i] !== undefined ? values[i] : '')
}, '').trim()
return function(props = {}) {
// Вычисляем финальный CSS с учётом props (упрощённо)
const el = document.createElement(tag)
const className = generateClassName()
injectStyle(className, cssText)
el.className = className
if (props.textContent) el.textContent = props.textContent
return el
}
}
}
const styled = {
div: createStyledEl('div'),
button: createStyledEl('button'),
span: createStyledEl('span'),
}
// Используем нашу мини-систему
const Card = styled.div`
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
font-family: sans-serif;
margin: 8px;
`
const PrimaryButton = styled.button`
background: #7b2ff7;
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
`
// Создаём элементы
const card = Card({ textContent: '' })
document.body.appendChild(card)
const title = document.createElement('h3')
title.textContent = 'CSS-in-JS компонент'
title.style.margin = '0 0 8px'
card.appendChild(title)
const btn = PrimaryButton({ textContent: 'Кнопка' })
card.appendChild(btn)
// Выводим результат
console.log('Инжектированные классы:', [...injectedStyles.keys()])
console.log('Количество инжектированных стилей:', injectedStyles.size)
console.log('Card className:', card.className)
console.log('Button className:', btn.className)
// Проверяем что стили инжектированы
const styleTag = document.getElementById('css-in-js-styles')
console.log('Style tag содержит правил:', styleTag.textContent.split('\n').filter(l => l.trim()).length)CSS-in-JS — это когда стили пишутся в JS, но результат — обычный CSS. Напиши итоговый HTML с CSS-стилями, которые имитируют то, что сгенерировал бы styled-components. Компонент `Card` должен иметь белый фон, тень и скруглённые углы. Компонент `Button` с пропсом `primary` — фиолетовый фон, без `primary` — прозрачный с фиолетовой рамкой.
Card: `background: white`, `border-radius: 12px`, `padding: 20px`, `box-shadow: 0 2px 8px rgba(0,0,0,0.1)`. Button primary: `background: #7b2ff7`, `color: white`. Button outline: `background: transparent`, `color: #7b2ff7`. Hover: `opacity: 0.85`.