CSS-in-JS

CSS-in-JS 是一种把样式纳入组件逻辑系统的方案,通过 JavaScript 管理样式生成、组合、主题和动态状态。

#type / concept #status / growing #tech / dev / frontend #resource / css #resource / css-in-js

[!info] related notes

CSS-in-JS

一句话定义

CSS-in-JS 是一类把样式定义、组合和注入交给 JavaScript 管理的方案,使样式可以直接响应组件的 props、state 和 theme。

核心机制 / 工作原理

CSS-in-JS 不是简单地”把 CSS 写进 JS 字符串”。

它更接近一套样式运行或编译系统,通常会负责:

  1. 为组件样式生成唯一 className。
  2. 生成或提取对应的 CSS。
  3. 把样式注入页面,或在构建期输出静态 CSS。
  4. 根据 props、state、theme 组合出不同样式。

从工程角度看,CSS-in-JS 让样式从”组件外部资源”变成”组件逻辑系统的一部分”。

代码示例

styled-components(运行时方案)

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

// 基础组件
const Button = styled.button`
  padding: 8px 16px;
  border-radius: 6px;
  background: ${props => (props.disabled ? '#ccc' : props.theme.primary)};
  color: white;
  cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
`

// 继承扩展
const DangerButton = styled(Button)`
  background: ${props => (props.disabled ? '#ccc' : '#ff4d4f')};
`

// 使用主题
const theme = { primary: '#1677ff' }

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button>提交</Button>
      <Button disabled>禁用</Button>
      <DangerButton>删除</DangerButton>
    </ThemeProvider>
  )
}

Emotion(运行时方案)

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
import styled from '@emotion/styled'

// css prop 方式
const style = css`
  color: hotpink;
  font-size: 16px;
`

function Component() {
  return <div css={style}>Hello</div>
}

// styled 方式
const Container = styled.div`
  display: flex;
  gap: ${props => props.gap || 8}px;
`

Vanilla Extract(编译时方案)

// styles.css.ts
import { style } from '@vanilla-extract/css'

export const container = style({
  display: 'flex',
  padding: 16,
  backgroundColor: '#f5f5f5',
})

// 根据变量生成变体
import { recipe } from '@vanilla-extract/recipes'

export const button = recipe({
  base: {
    padding: '8px 16px',
    borderRadius: 6,
  },
  variants: {
    color: {
      primary: { background: '#1677ff', color: 'white' },
      danger: { background: '#ff4d4f', color: 'white' },
    },
    size: {
      small: { fontSize: 12 },
      large: { fontSize: 18 },
    },
  },
})

编译时方案在构建期生成静态 CSS,运行时零开销。

SSR 考虑

运行时 CSS-in-JS 在服务端渲染时需要特殊处理:

  1. 样式收集:服务端渲染时收集组件用到的样式,注入到 HTML 的 <style> 标签中。
  2. hydration 一致性:客户端 hydrate 时,必须确保样式与服务端一致,否则会出现样式闪烁(FOUC)。
// styled-components SSR 示例
import { ServerStyleSheet } from 'styled-components'

const sheet = new ServerStyleSheet()
const html = renderToString(sheet.collectStyles(<App />))
const styleTags = sheet.getStyleTags()  // 注入到 <head>
  1. Next.js 集成:需要在 _document 中配置样式收集逻辑,否则首屏会没有样式。

编译时方案(Vanilla Extract、Linaria)不涉及运行时注入,SSR 无额外成本。

性能对比

方案运行时开销SSR 复杂度包体积影响动态样式能力
styled-components / Emotion中等较大
Vanilla Extract / Linaria
StyleX(Meta)极小中等
CSS Modules

运行时方案在频繁切换样式(如动画、大量列表项)时可能有性能瓶颈,因为每次渲染都需要计算样式和插入 <style> 标签。

边界与易混淆点

  • CSS-in-JS 的主要优势是动态性和主题系统,不是所有项目都需要这层能力。
  • 运行时 CSS-in-JS 会带来一定成本:生成样式、插入 style 标签、SSR 样式收集和 hydration 一致性问题。
  • 调试时看到的 className 可能不如手写类名直观。
  • 它不应该替代浏览器原生 CSS 的所有能力:@media、CSS variables、container queries、cascade layers 等仍应优先交给 CSS。
  • 现代 CSS-in-JS 已经分化成两类:
    • 运行时方案,如 styled-componentsEmotion
    • 编译时方案,如 Vanilla ExtractLinariaStyleX
  • 如果项目更看重首屏性能、SSR 稳定性和运行时开销,往往会更偏向编译时方案、CSS Modules 或原生 CSS 变量。
  • React Server Components 的兴起让运行时 CSS-in-JS 方案面临挑战,因为 RSC 中不能使用 useState 等客户端特性,而 styled-components 等依赖客户端运行时。
创建于 2026/5/14 更新于 2026/5/27