Emotion
React 生态最成熟的高灵活度 CSS-in-JS 库之一
Emotion 提供 styled 组件和 css prop 两种核心写法,兼顾组件化样式和灵活表达,是 MUI 默认样式引擎的重要基础。
Emotion
高性能、灵活的 CSS-in-JS 库,提供
styled组件 API 和独特的cssprop 两种主流样式编写方式,是 MUI 等主流组件库的默认样式引擎。
技术简介说明
Emotion 是一个为 JavaScript 而生的 CSS-in-JS 样式库,它允许开发者使用 JavaScript 编写样式,同时保留完整 CSS 能力——包括嵌套选择器、伪类、媒体查询、动画关键帧等。Emotion 的核心设计哲学是在运行时(runtime)通过 JavaScript 动态生成作用域化的 CSS 类名并注入页面,兼具 styled-components 的组件化思路和更灵活的 css prop 直接样式能力。
Emotion 采用 monorepo 架构,提供多个独立包:@emotion/react(React 集成,含 css prop 和 ThemeProvider)、@emotion/styled(styled 组件 API)、@emotion/css(框架无关的 vanilla 用法)等。它支持字符串模板和对象两种样式语法,自动处理浏览器前缀、样式去重和源码映射,配合 Babel 插件可实现样式压缩和类名优化。
截至 2025-2026 年,Emotion 仍是 npm 上周下载量最高的 CSS-in-JS 库(@emotion/react 约 1900 万次/周),主要驱动力来自 MUI(Material UI)v5/v6 的深度集成。但受 React Server Components(RSC)不兼容、Next.js App Router 集成复杂等现代 React 架构变化影响,Emotion 和整个运行时 CSS-in-JS 阵营正面临向零运行时方案(vanilla-extract、Panda CSS、CSS Modules 等)迁移的行业趋势压力。
基本信息
| 项目 | 详情 |
|---|---|
| 官网 | https://emotion.sh |
| GitHub | https://github.com/emotion-js/emotion |
| License | MIT |
| 最新版本 | @emotion/react v11.14.0(2024.12);@emotion/styled v11.14.1(2025.06);@emotion/jest v11.14.2(2025.11) |
| GitHub Stars | 18k+ |
| npm 周下载量 | @emotion/react ~19M;@emotion/styled ~12.8M(2025 年中) |
| 维护者 | Emotion 团队(mitchellhamilton、tkh44、Andarist 等核心贡献者),社区驱动,Open Collective 赞助 |
| 包管理 | Monorepo(pnpm + changesets) |
| 首次发布 | 2017 年 |
| React 19 兼容性 | v11 未完全兼容,需 --legacy-peer-deps;完整 React 19 支持计划在 v12 |
快速上手
安装
# React 项目基础(css prop + ThemeProvider)
npm install @emotion/react
# 加上 styled 组件 API
npm install @emotion/react @emotion/styled
# 框架无关用法(非 React 项目)
npm install @emotion/css
# 开发依赖:Babel 插件(推荐,用于样式优化和源码映射)
npm install -D @emotion/babel-plugin基础配置
Babel 配置(推荐)
// .babelrc
{
"plugins": [
"@emotion"
// ⚠️ @emotion/babel-plugin 必须放在 plugins 数组的第一位
]
}TypeScript 配置
// tsconfig.json
{
"compilerOptions": {
"jsxImportSource": "@emotion/react"
// 启用 css prop 支持
}
}主题类型声明
// types/emotion.d.ts
import '@emotion/react'
declare module '@emotion/react' {
export interface Theme {
colors: {
primary: string
secondary: string
background: string
text: string
}
spacing: {
sm: string
md: string
lg: string
}
breakpoints: {
mobile: string
tablet: string
desktop: string
}
}
}最小示例
方式一:css prop(推荐用于轻量样式)
import { css } from '@emotion/react'
function App() {
return (
<div
css={css`
display: flex;
flex-direction: column;
align-items: center;
padding: 24px;
background-color: #f5f5f5;
`}
>
<h1
css={{
color: '#333',
fontSize: '2rem',
marginBottom: '16px',
}}
>
Hello Emotion
</h1>
<p
css={(theme) => css`
color: ${theme.colors.text};
font-size: 1rem;
`}
>
使用 css prop 直接编写样式
</p>
</div>
)
}方式二:styled API(推荐用于可复用组件)
import styled from '@emotion/styled'
const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
padding: 24px;
background-color: ${(props) => props.theme.colors.background};
`
const Title = styled.h1({
color: '#333',
fontSize: '2rem',
marginBottom: '16px',
})
// 支持动态 props
const Button = styled.button<{ variant: 'primary' | 'secondary' }>`
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
background-color: ${(props) =>
props.variant === 'primary' ? '#007bff' : '#6c757d'};
color: white;
&:hover {
opacity: 0.9;
}
`
function App() {
return (
<Container>
<Title>Hello Emotion</Title>
<Button variant="primary">点击我</Button>
</Container>
)
}方式三:完整应用(含主题)
import React from 'react'
import { ThemeProvider } from '@emotion/react'
import styled from '@emotion/styled'
const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d',
background: '#ffffff',
text: '#333333',
},
spacing: {
sm: '8px',
md: '16px',
lg: '24px',
},
breakpoints: {
mobile: '480px',
tablet: '768px',
desktop: '1024px',
},
}
const Card = styled.div`
background: ${(props) => props.theme.colors.background};
color: ${(props) => props.theme.colors.text};
padding: ${(props) => props.theme.spacing.lg};
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
@media (max-width: ${(props) => props.theme.breakpoints.tablet}) {
padding: ${(props) => props.theme.spacing.md};
}
`
function App() {
return (
<ThemeProvider theme={theme}>
<Card>
<h2>主题化卡片</h2>
<p>样式从 theme 对象中读取设计令牌</p>
</Card>
</ThemeProvider>
)
}
export default App核心概念与架构
运行时样式注入机制
Emotion 是一个运行时 CSS-in-JS 库,其核心工作流程为:
JavaScript 样式定义
↓
Babel 插件编译优化(可选)
↓
运行时序列化 + 哈希生成类名
↓
Stylis 预处理(自动前缀、嵌套展平)
↓
<style> 标签注入 <head>
↓
CSS 类名绑定到 DOM 元素
- 样式序列化(Serialization):将 JavaScript 对象或模板字符串转换为 CSS 字符串
- 哈希类名生成:基于样式内容生成唯一哈希类名(如
css-1a2b3c),确保样式作用域隔离 - Stylis 预处理:内置 Stylis 预处理器,自动添加浏览器前缀、展平嵌套规则
- 样式注入:通过
<style>标签动态注入到<head>中,使用insertRuleAPI 提升性能 - 缓存与去重:已注入的样式规则会被缓存,避免重复注入
包架构(Monorepo)
@emotion/
├── react # React 集成核心(css prop、ThemeProvider、useTheme)
├── styled # styled 组件 API
├── css # 框架无关的 vanilla API
├── cache # 样式缓存和 sheet 管理
├── serialize # CSS 序列化器
├── sheet # 样式表操作(insertRule / <style> 标签)
├── hash # 高性能哈希函数
├── weak-memoize # 弱引用记忆化
├── babel-plugin # Babel 编译优化插件
├── jest # Jest 测试序列化和匹配器
├── server # SSR 样式提取
├── eslint-plugin # ESLint 规则
├── native # React Native 适配(实验性)
└── primitives # 基础组件原语(实验性)
两种 API 范式
| 范式 | 包 | 核心理念 | 适用场景 |
|---|---|---|---|
css prop | @emotion/react | 将样式作为 prop 直接附加到任意 JSX 元素 | 一次性样式、快速原型、布局微调 |
styled API | @emotion/styled | 创建带样式的可复用组件 | 设计系统、组件库、需要语义化的场景 |
核心特性
1. 双 API 范式(css prop + styled)
Emotion 同时提供 css prop 和 styled API,开发者可根据场景自由选择。css prop 不需要包装组件,可直接在 JSX 元素上编写样式;styled API 则创建独立的带样式组件。两种 API 共享相同的样式能力,可以混合使用。
2. 字符串与对象双语法
支持模板字符串(类 CSS 语法)和 JavaScript 对象两种样式定义方式,均可使用嵌套、伪类、媒体查询等 CSS 特性:
// 模板字符串语法
const A = styled.div`
color: red;
&:hover { color: blue; }
@media (min-width: 768px) { font-size: 1.2rem; }
`
// 对象语法(TypeScript 推断更友好)
const B = styled.div({
color: 'red',
'&:hover': { color: 'blue' },
'@media (min-width: 768px)': { fontSize: '1.2rem' },
})3. 主题系统(ThemeProvider)
内置 ThemeProvider、useTheme() hook 和 withTheme() HOC,通过 React Context 将主题对象注入组件树。支持嵌套 ThemeProvider 实现局部主题覆盖。
import { ThemeProvider, useTheme } from '@emotion/react'
function ThemedComponent() {
const theme = useTheme()
return <div css={{ color: theme.colors.primary }}>主题色文字</div>
}4. 高性能运行时
- 使用
insertRuleAPI 批量注入样式,比逐个插入<style>标签更高效 - 内置样式缓存和记忆化(
weak-memoize),相同样式不重复计算 - 配合 Babel 插件预编译,运行时开销大幅降低
- 基准测试:1000 次动态渲染约 38ms(配合 Babel 插件),优于 styled-components 的 62ms
5. SSR 支持
提供完整的 @emotion/server 包,支持多种 SSR 策略:
renderStylesToString:内联关键 CSS 到 HTML 字符串renderStylesToNodeStream:流式 SSRextractCriticalToChunks+constructStyleTagsFromChunks:提取关键 CSS 集中注入<head>
6. 源码映射(Source Maps)
开发环境下自动生成 CSS 源码映射,调试时可在 DevTools 中直接定位到原始 JS 文件中的样式定义位置。Babel 插件会增强源码映射精度。
7. 样式组合(Composition)
css 工具函数支持样式组合,可以将多个样式对象/类名合并,cx 函数(来自 @emotion/css)用于条件性合并类名:
import { css, cx } from '@emotion/css'
const base = css`
padding: 8px;
border-radius: 4px;
`
const primary = css`
background: #007bff;
color: white;
`
const large = css`
padding: 16px;
font-size: 1.2rem;
`
function Badge({ isLarge }: { isLarge: boolean }) {
return <span className={cx(base, primary, isLarge && large)}>标签</span>
}8. TypeScript 深度集成
通过模块声明(module augmentation)为 Theme 接口添加自定义类型,使 props.theme 在所有 styled 组件和 css prop 中获得完整的类型推断和自动补全。
9. Babel 插件优化
@emotion/babel-plugin 提供:
- 样式压缩和类名优化
- 组件名自动注入到生成类名(如
css-abc123-Button) - 源码映射增强
- 可选的样式提取(extract mode)为静态 CSS 文件
- sourceMap 支持
10. Jest 测试工具
@emotion/jest 提供 Jest 序列化器和自定义匹配器,使快照测试输出可读的 CSS 样式而非哈希类名:
import serializer from '@emotion/jest/serializer'
expect.addSnapshotSerializer(serializer)
test('按钮样式', () => {
const { container } = render(<Button>Click</Button>)
expect(container.firstChild).toMatchEmotionSnapshot()
})生态图
Emotion 生态
│
├── 核心包
│ ├── @emotion/react # React 核心(css prop、ThemeProvider、useTheme)
│ ├── @emotion/styled # styled 组件 API
│ ├── @emotion/css # 框架无关的 vanilla CSS-in-JS
│ └── @emotion/cache # 样式缓存配置
│
├── 工具链
│ ├── @emotion/babel-plugin # Babel 编译优化
│ ├── @emotion/server # SSR 样式提取
│ ├── @emotion/jest # Jest 测试序列化器
│ └── @emotion/eslint-plugin # ESLint 规则
│
├── 底层能力
│ ├── @emotion/serialize # CSS 序列化
│ ├── @emotion/sheet # 样式表管理
│ ├── @emotion/hash # 哈希生成
│ └── @emotion/weak-memoize # 记忆化工具
│
├── 第三方集成
│ ├── MUI (Material UI) # 默认样式引擎(v5/v6)
│ ├── Chakra UI # 部分版本使用 Emotion
│ ├── Theme UI # 基于 Emotion 的主题化 UI
│ ├── tss-react # Emotion + TypeScript 样式方案
│ ├── @xstyled/emotion # xstyled 的 Emotion 适配
│ └── gatsby-plugin-emotion # Gatsby 官方集成插件
│
├── React Native(实验性)
│ └── @emotion/native # React Native 样式适配
│
└── 框架集成
├── Next.js # Pages Router 良好支持;App Router 需要额外配置
├── Gatsby # 官方插件 gatsby-plugin-emotion
├── Remix # 客户端渲染可用
└── Create React App # 通过 Babel Macros 支持
适用场景
1. MUI 组件库项目
MUI v5/v6 将 Emotion 作为默认样式引擎。使用 MUI 意味着已经间接依赖 Emotion,可以直接利用 Emotion 的 styled API 和主题系统扩展 MUI 组件样式,无需额外引入样式库。
2. 设计系统与组件库开发
Emotion 的主题系统、样式组合能力和 TypeScript 类型安全使其适合构建设计系统。通过 ThemeProvider 和模块化的主题令牌,可以实现一致的视觉语言和灵活的定制能力。
3. 需要动态样式的应用
运行时 CSS-in-JS 天然擅长处理动态、依赖 props 或状态的样式。Emotion 的 styled 组件可以直接根据 props 计算样式值,无需预定义 CSS 类或使用 CSS 变量 hack。
4. 从 styled-components 迁移的项目
Emotion 的 API 与 styled-components 高度相似(模板字符串 + props 函数),迁移成本较低。特别是 styled-components 进入维护模式后,Emotion 成为同类运行时方案中最成熟的替代选择。
5. 客户端渲染(CSR)应用
Emotion 在纯客户端渲染场景中表现最佳——无 SSR 复杂性、无 RSC 兼容性问题。SPA(单页应用)、后台管理系统、仪表盘等 CSR 场景非常适合使用 Emotion。
6. 快速原型和迭代
css prop 允许直接在 JSX 元素上编写样式而无需创建单独的 styled 组件,非常适合快速原型开发和频繁迭代的设计阶段。
7. 需要样式测试的项目
@emotion/jest 提供快照测试序列化器,使测试输出中包含可读的 CSS 规则而非哈希类名,提升样式测试的可维护性。
开发与工程化
项目结构建议
src/
├── theme/
│ ├── index.ts # 主题对象导出
│ ├── tokens.ts # 设计令牌(颜色、间距、字体等)
│ └── types.ts # 主题 TypeScript 类型
├── components/
│ ├── Button/
│ │ ├── index.ts
│ │ ├── Button.tsx # 组件逻辑
│ │ └── styles.ts # Emotion 样式(可选拆分)
│ └── ...
├── styles/
│ ├── global.ts # 全局样式
│ └── mixins.ts # 样式 mixins / 复用片段
└── App.tsx
全局样式
import { Global, css } from '@emotion/react'
function GlobalStyles() {
return (
<Global
styles={css`
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
sans-serif;
-webkit-font-smoothing: antialiased;
}
`}
/>
)
}动画定义
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const fadeIn = keyframes`
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
`
const AnimatedCard = styled.div`
animation: ${fadeIn} 0.3s ease-out;
`Babel 插件配置(完整选项)
// .babelrc
{
"plugins": [
[
"@emotion",
{
"sourceMap": true, // 开发环境开启源码映射
"autoLabel": "dev-only", // 开发环境自动添加标签
"labelFormat": "[local]", // 类名格式:css-hash-ComponentName
"importMap": { // 自定义导入映射
"@emotion/styled": {
"styled": {
"canonicalImport": ["@emotion/styled", "default"],
"computedStyles": ["Button"]
}
}
}
}
]
]
}与 Next.js 集成
// pages/_app.tsx(Pages Router)
import { CacheProvider } from '@emotion/react'
import createCache from '@emotion/cache'
import { ThemeProvider } from '@emotion/react'
import theme from '@/theme'
const clientSideEmotionCache = createCache({ key: 'css' })
function MyApp({ Component, pageProps }) {
return (
<CacheProvider value={clientSideEmotionCache}>
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
)
}// pages/_document.tsx(Pages Router SSR)
import { Html, Head, Main, NextScript } from 'next/document'
import createCache from '@emotion/cache'
import { renderStylesToString } from '@emotion/server'
export default function Document({ html, styles }) {
return (
<Html lang="zh-CN">
<Head />
<body dangerouslySetInnerHTML={{ __html: html }}>
{styles}
<Main />
<NextScript />
</body>
</Html>
)
}
Document.getInitialProps = async (ctx) => {
const cache = createCache({ key: 'css' })
const extractStyles = renderStylesToString
// ... 自定义 SSR 逻辑
}注意:Next.js App Router 下 Emotion 兼容性不佳,存在 Turbopack hydration 错误等问题。建议使用 Pages Router 或考虑迁移至 CSS Modules / Tailwind CSS。
Transient Props(瞬态属性)
Emotion styled 组件默认会将所有 props 转发到 DOM,自定义非标准属性会产生 React 警告。使用 shouldForwardProp 或 transient props($ 前缀)避免:
// 方式一:shouldForwardProp
const Box = styled('div', {
shouldForwardProp: (prop) => prop !== 'isHighlighted',
})<{ isHighlighted: boolean }>`
background: ${(props) => (props.isHighlighted ? 'yellow' : 'white')};
`
// 方式二:transient props($ 前缀,Emotion 11+)
const Box = styled.div<{ $isHighlighted: boolean }>`
background: ${(props) => (props.$isHighlighted ? 'yellow' : 'white')};
`
// $ 前缀的 props 自动不会转发到 DOM性能与安全
性能特征
| 指标 | 表现 | 说明 |
|---|---|---|
| 运行时包大小 | @emotion/react ~7KB gzip | 比 styled-components(~12KB)更轻量 |
| 运行时渲染 | ~38ms / 1000 次动态渲染(配 Babel 插件) | 优于 styled-components 的 ~62ms |
| 样式注入方式 | insertRule API | 比插入 <style> 标签更快 |
| 缓存机制 | weak-memoize | 相同样式不重复计算 |
| 首屏影响 | 运行时计算开销 | 存在 JS 执行 + CSS 注入延迟 |
| Tree-shaking | 部分支持 | 按包独立引入可减小体积 |
性能优化建议
- 始终使用
@emotion/babel-plugin:预编译可显著减少运行时开销 - 避免在渲染路径中创建样式对象:将
css调用提到组件外部或使用useMemo - 使用
memo包裹 styled 组件:防止不必要的重渲染 - 减少动态样式变化:频繁变化的 props 会导致重复样式计算和注入
- SSR 时提取关键 CSS:使用
extractCriticalToChunks避免 FOUC
安全考量
- XSS 风险:Emotion 样式完全由 JavaScript 控制,如果样式值中包含用户输入(如
userInput作为 CSS 值),需确保输入已清洗,避免expression()、url()等 CSS 注入向量 - CSP 兼容性:运行时注入
<style>标签需要 CSP 策略允许unsafe-inline或使用 nonce。可通过@emotion/cache配置 nonce:const cache = createCache({ key: 'css', nonce: 'your-nonce-value', }) - CSS 注入攻击:避免将不可信数据直接作为
css值或模板字符串插值
技术对比
与同类方案对比
| 特性 | Emotion | styled-components | Tailwind CSS | CSS Modules | vanilla-extract | Panda CSS |
|---|---|---|---|---|---|---|
| 类型 | 运行时 CSS-in-JS | 运行时 CSS-in-JS | 原子化 Utility | 编译时作用域 CSS | 编译时 CSS-in-TS | 编译时 + 运行时 |
| 包大小 | ~7KB gzip | ~12KB gzip | ~0KB(纯 CSS) | 0KB | 0KB(编译时提取) | ~0KB |
| API 风格 | css prop + styled | styled | Utility classes | .module.css | .css.ts 文件 | 食谱(recipes)+ cva |
| TypeScript | ✅ 优秀(模块增强) | ✅ 良好 | ✅ 支持 | ⚠️ 需额外配置 | ✅ 一流支持 | ✅ 一流支持 |
| React 19 / RSC | ❌ 不兼容 | ❌ 不兼容 | ✅ 完全兼容 | ✅ 完全兼容 | ✅ 完全兼容 | ✅ 完全兼容 |
| Next.js App Router | ⚠️ 复杂配置 | ⚠️ 复杂配置 | ✅ 原生支持 | ✅ 原生支持 | ✅ 原生支持 | ✅ 原生支持 |
| 主题系统 | ✅ 内置 ThemeProvider | ✅ 内置 ThemeProvider | ⚠️ 需 Tailwind 配置 | ❌ 手动管理 | ✅ 主题 API | ✅ 内置 |
| SSR | ✅ 完整支持 | ✅ 完整支持 | ✅ 零配置 | ✅ 零配置 | ✅ 零配置 | ✅ 零配置 |
| 动态样式 | ✅ 天然支持 | ✅ 天然支持 | ⚠️ 需 CSS 变量 | ⚠️ 有限 | ⚠️ 需 CSS 变量 | ✅ 支持 |
| 运行时开销 | 有(insertRule) | 有(insertRule) | 无 | 无 | 无 | 极低 |
| 学习曲线 | 中 | 中 | 中高 | 低 | 中 | 中高 |
| npm 周下载量 | ~19M | ~10.9M | ~20M+ | N/A(Next.js 内置) | ~1M | ~0.3M |
| 社区趋势 | 稳定/微降 | 维护模式 | 强劲增长 | 稳定 | 增长中 | 增长中 |
| 最佳搭档 | MUI | 独立项目 | 任何框架 | 任何框架 | TypeScript 项目 | 设计系统 |
Emotion vs styled-components
| 维度 | Emotion | styled-components |
|---|---|---|
| css prop | ✅ 独有,灵活直接 | ❌ 不支持 |
| API 灵活性 | 双 API(css prop + styled) | 单 API(styled only) |
| 包大小 | ~7KB gzip(更小) | ~12KB gzip |
| 运行时性能 | ~38ms / 1000 次渲染 | ~62ms / 1000 次渲染 |
| 对象语法 | ✅ 原生支持 | ⚠️ v6 开始支持 |
| 项目状态 | 持续维护,计划 v12 | 已进入维护模式(2024) |
| 生态绑定 | MUI(巨大存量) | 独立社区 |
最佳实践
1. 始终使用 Babel 插件
npm install -D @emotion/babel-plugin在构建流程中加入 @emotion/babel-plugin,可获得:类名可读化(调试友好)、样式压缩、自动源码映射、运行时性能提升。
2. 设计令牌集中管理
// theme/tokens.ts
export const tokens = {
colors: {
primary: { 50: '#eff6ff', 500: '#3b82f6', 900: '#1e3a8a' },
neutral: { 50: '#fafafa', 500: '#6b7280', 900: '#111827' },
},
spacing: { 0: '0', 1: '4px', 2: '8px', 3: '12px', 4: '16px', 6: '24px', 8: '32px' },
fontSizes: { xs: '12px', sm: '14px', base: '16px', lg: '18px', xl: '20px' },
radii: { sm: '4px', md: '8px', lg: '12px', full: '9999px' },
} as const3. 样式复用使用 css 工具函数
import { css } from '@emotion/react'
const flexCenter = css`
display: flex;
align-items: center;
justify-content: center;
`
const cardBase = css`
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 16px;
`
// 组合使用
const CenteredCard = styled.div`
${flexCenter};
${cardBase};
`4. 避免渲染路径中的样式对象创建
// ❌ 不好:每次渲染都创建新对象
function Bad() {
return <div css={{ color: 'red', padding: '16px' }}>内容</div>
}
// ✅ 好:提取到组件外部或使用变量
const redStyle = css`
color: red;
padding: 16px;
`
function Good() {
return <div css={redStyle}>内容</div>
}5. 为 styled 组件使用有意义的类名
配合 Babel 插件的 labelFormat 选项,使生成的类名包含组件名:
// .babelrc
{
"plugins": [
["@emotion", { "labelFormat": "[filename]--[local]" }]
]
}
// 生成类名如:css-abc123-Button--Container6. SSR 场景使用 CacheProvider
import createCache from '@emotion/cache'
import { CacheProvider } from '@emotion/react'
// 服务端每次请求创建独立 cache,避免样式泄漏
const cache = createCache({ key: 'my-app' })
function App({ children }) {
return <CacheProvider value={cache}>{children}</CacheProvider>
}7. 条件样式使用函数式 css prop
// ✅ 利用 theme 参数避免重复 useTheme
<p css={(theme) => css`color: ${theme.colors.primary};`}>
主题色文字
</p>技术局限与边界
1. React Server Components(RSC)不兼容
这是 Emotion 在 2025-2026 年面临的最核心局限。 Emotion 依赖运行时 JavaScript 在浏览器中注入样式,而 RSC 在服务端渲染且零 JS 发送到客户端,两者架构根本不兼容。唯一解法是在使用 Emotion 的组件上添加 'use client',但这会完全抵消 RSC 的性能优势。这不是版本更新能解决的问题,是架构层面的矛盾。
2. Next.js App Router 集成复杂
在 Next.js 15 的 App Router 下,Emotion 存在已知问题:
- Turbopack 下的 hydration 错误
- 需要手动配置
CacheProvider和样式注册表 - 官方(Vercel)推荐 Tailwind CSS 或 CSS Modules,未提供 Emotion 的 App Router 示例
3. 运行时性能开销
尽管在 CSS-in-JS 中属于较优,但与零运行时方案相比,Emotion 仍存在:
- 首屏 JS 执行开销(样式序列化 + 注入)
- 动态样式变化时的重复计算
- 阻塞渲染的
<style>标签注入
4. CSP(内容安全策略)限制
运行时注入 <style> 标签需要 CSP 允许 unsafe-inline 或配置 nonce,在安全要求严格的企业环境中可能成为障碍。
5. React DevTools 干扰
Emotion 运行时会插入内部组件(如 Global、ThemeProvider),使 React DevTools 组件树变得杂乱,影响调试体验。
6. 社区发展方向不确定
- styled-components 已进入维护模式,CSS-in-JS 阵营整体面临信心危机
- MUI 正在开发零运行时方案(Pigment CSS),计划未来替代 Emotion
- 新项目普遍推荐 Tailwind CSS / CSS Modules / vanilla-extract 等零运行时方案
- Emotion 的长期发展方向取决于 v12(React 19 兼容版本)能否及时推出
7. 包体积与性能权衡
虽然 @emotion/react 的 gzip 大小(~7KB)已经较小,但对比 Tailwind CSS(运行时 0KB)或 CSS Modules(编译时 0KB),仍有明确的性能差距。
学习资源
官方文档
- Emotion 官方文档 — 完整 API 参考和指南
- css prop 文档 — Emotion 独有的核心特性
- styled API 文档 — styled 组件用法
- Theming 文档 — 主题系统配置
- TypeScript 文档 — TypeScript 集成指南
- SSR 文档 — 服务端渲染方案
- Babel 插件文档 — 编译优化配置
教程与博客
- Emotion in React — LogRocket Blog — 全面的 Emotion React 使用指南
- Styled Components/Emotion Compared — Caisy — 两者详细对比
- CSS-in-JS: Styled Components vs Emotion — NamasteDev — 入门级对比教程
- Styling in React with Emotion — CodingCops — 实践教程
- Optimizing Emotion in React — DEPT Engineering — 性能优化深度文章
对比与趋势
- Emotion vs styled-components in 2026 — PkgPulse — 2026 年终对比
- The State of CSS-in-JS in 2026 — OpenReplay — 2026 年 CSS-in-JS 全景
- The State of CSS-in-JS in 2026 — PkgPulse — CSS-in-JS 是否已死
- CSS in React Server Components — Josh W. Comeau — RSC 与 CSS-in-JS 兼容性深度分析
社区
- GitHub Discussions — 官方讨论区
- React 19 兼容性讨论 — v12 路线图
- Next.js CSS-in-JS 指南 — Next.js 官方 CSS-in-JS 集成说明
2026 年现状
市场地位
Emotion 仍是 npm 上周下载量最高的 CSS-in-JS 库(@emotion/react 约 1900 万次/周),但这主要得益于 MUI 的巨大存量用户。作为独立选型,新项目选择 Emotion 的比例正在下降。
React 19 / Server Components 困境
Emotion v11 明确不兼容 React 19,完整的 React 19 支持被推迟到 v12。在 RSC 架构下,Emotion 的运行时注入模型与零 JS 客户端的服务端渲染理念根本冲突。要在 App Router 中使用,必须为所有使用 Emotion 的组件添加 'use client',这实质上放弃了 RSC 的所有优势(更小的 JS bundle、更快的首次加载、服务端数据访问)。
行业趋势
- styled-components 已于 2024 年进入维护模式,不再积极开发新功能
- MUI 正在开发 Pigment CSS(零运行时方案),计划在未来的 v7+ 版本中替代 Emotion
- Next.js 官方推荐 Tailwind CSS 和 CSS Modules,CSS-in-JS 在 App Router 下被视为二等公民
- 社区共识 正在转向零运行时方案:vanilla-extract、Panda CSS、StyleX、CSS Modules
选型建议
| 场景 | 建议 |
|---|---|
| 已有 MUI + Emotion 存量项目 | 继续使用,暂无紧迫迁移必要;关注 MUI v7 的 Pigment CSS 进展 |
| 从 styled-components 迁移 | Emotion 可作为过渡方案(API 相似、迁移成本低),但建议直接评估零运行时方案 |
| Next.js App Router 新项目 | ❌ 不推荐 Emotion,优先选择 Tailwind CSS 或 CSS Modules |
| 纯 CSR / SPA 应用 | ✅ 可以使用,Emotion 在此场景下体验良好 |
| 设计系统 / 组件库 | ⚠️ 可用但需谨慎,考虑零运行时方案作为长期策略 |
| 快速原型 / 内部工具 | ✅ 适合,css prop 的灵活性在原型开发中优势明显 |
未来展望
Emotion 短期内(2026 年)不会消亡——MUI 生态的巨大存量和 CSS-in-JS 的运行时灵活性仍然有其不可替代的价值。但长期来看,随着 React 生态向 Server Components 和零运行时方向演进,Emotion 的核心运行时模型正面临架构层面的挑战。v12 能否及时解决 React 19 兼容问题、以及是否会探索零运行时方向,将决定 Emotion 的长期命运。