Next.js CSS-in-JS #
一、CSS-in-JS概述 #
1.1 什么是CSS-in-JS #
CSS-in-JS是一种在JavaScript中编写CSS的技术:
tsx
const Button = styled.button`
padding: 0.5rem 1rem;
background-color: blue;
color: white;
border-radius: 0.25rem;
`
1.2 优缺点 #
优点
- 组件级作用域
- 动态样式
- 类型安全
- 主题支持
缺点
- 运行时开销
- 学习成本
- SSR配置复杂
1.3 常用库 #
| 库 | 特点 |
|---|---|
| styled-components | 流行,功能完整 |
| emotion | 性能好,灵活 |
| stitches | 零运行时 |
| vanilla-extract | 零运行时,类型安全 |
二、styled-components #
2.1 安装 #
bash
npm install styled-components
npm install -D @types/styled-components
2.2 基本用法 #
tsx
'use client'
import styled from 'styled-components'
const Container = styled.div`
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
`
const Title = styled.h1`
font-size: 2rem;
color: #1f2937;
`
export default function Page() {
return (
<Container>
<Title>页面标题</Title>
</Container>
)
}
2.3 Props #
tsx
interface ButtonProps {
$primary?: boolean
}
const Button = styled.button<ButtonProps>`
padding: 0.5rem 1rem;
border-radius: 0.25rem;
background-color: ${props => props.$primary ? 'blue' : 'gray'};
color: white;
`
export default function Form() {
return (
<div>
<Button $primary>主要按钮</Button>
<Button>次要按钮</Button>
</div>
)
}
2.4 SSR配置 #
next.config.ts
typescript
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
compiler: {
styledComponents: true,
},
}
export default nextConfig
Registry组件
tsx
'use client'
import { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
export default function StyledComponentsRegistry({
children,
}: {
children: React.ReactNode
}) {
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement()
styledComponentsStyleSheet.instance.clearTag()
return <>{styles}</>
})
if (typeof window !== 'undefined') {
return <>{children}</>
}
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
)
}
根布局使用
tsx
import StyledComponentsRegistry from './registry'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-CN">
<body>
<StyledComponentsRegistry>
{children}
</StyledComponentsRegistry>
</body>
</html>
)
}
三、Emotion #
3.1 安装 #
bash
npm install @emotion/react @emotion/styled
3.2 基本用法 #
tsx
'use client'
import styled from '@emotion/styled'
const Container = styled.div`
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
`
export default function Page() {
return <Container>内容</Container>
}
3.3 css prop #
tsx
'use client'
import { css } from '@emotion/react'
const containerStyle = css`
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
`
export default function Page() {
return <div css={containerStyle}>内容</div>
}
3.4 主题 #
tsx
'use client'
import { ThemeProvider } from '@emotion/react'
import styled from '@emotion/styled'
const theme = {
colors: {
primary: '#3b82f6',
secondary: '#64748b',
},
spacing: {
sm: '0.5rem',
md: '1rem',
lg: '2rem',
},
}
const Button = styled.button`
padding: ${props => props.theme.spacing.md};
background-color: ${props => props.theme.colors.primary};
color: white;
`
export default function App({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider theme={theme}>
<Button>主题按钮</Button>
</ThemeProvider>
)
}
四、Stitches #
4.1 安装 #
bash
npm install @stitches/react
4.2 配置 #
tsx
import { createStitches } from '@stitches/react'
export const { styled, css, globalCss, keyframes, getCssText } = createStitches({
theme: {
colors: {
primary: '#3b82f6',
secondary: '#64748b',
text: '#1f2937',
background: '#ffffff',
},
space: {
1: '0.25rem',
2: '0.5rem',
4: '1rem',
6: '1.5rem',
8: '2rem',
},
radii: {
sm: '0.25rem',
md: '0.5rem',
lg: '1rem',
},
},
})
4.3 使用 #
tsx
'use client'
import { styled } from '@/stitches.config'
const Button = styled('button', {
padding: '$4',
backgroundColor: '$primary',
color: 'white',
borderRadius: '$md',
border: 'none',
cursor: 'pointer',
variants: {
variant: {
primary: {
backgroundColor: '$primary',
},
secondary: {
backgroundColor: '$secondary',
},
},
size: {
sm: {
padding: '$2 $4',
fontSize: '0.875rem',
},
md: {
padding: '$4 $6',
},
lg: {
padding: '$6 $8',
fontSize: '1.125rem',
},
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
})
export default function Form() {
return (
<div>
<Button variant="primary" size="lg">主要按钮</Button>
<Button variant="secondary" size="sm">次要按钮</Button>
</div>
)
}
4.4 SSR配置 #
tsx
import { getCssText } from '@/stitches.config'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-CN">
<head>
<style id="stitches" dangerouslySetInnerHTML={{ __html: getCssText() }} />
</head>
<body>{children}</body>
</html>
)
}
五、Vanilla Extract #
5.1 安装 #
bash
npm install @vanilla-extract/css @vanilla-extract/next-plugin
5.2 配置 #
typescript
import type { NextConfig } from 'next'
import { createVanillaExtractPlugin } from '@vanilla-extract/next-plugin'
const withVanillaExtract = createVanillaExtractPlugin()
const nextConfig: NextConfig = {}
export default withVanillaExtract(nextConfig)
5.3 定义样式 #
typescript
import { style, createTheme } from '@vanilla-extract/css'
export const [themeClass, vars] = createTheme({
color: {
primary: '#3b82f6',
secondary: '#64748b',
},
space: {
small: '0.5rem',
medium: '1rem',
large: '2rem',
},
})
export const container = style({
maxWidth: '1200px',
margin: '0 auto',
padding: vars.space.medium,
})
export const button = style({
padding: `${vars.space.small} ${vars.space.medium}`,
backgroundColor: vars.color.primary,
color: 'white',
borderRadius: '0.25rem',
border: 'none',
cursor: 'pointer',
})
5.4 使用 #
tsx
import { container, button, themeClass } from './styles.css'
export default function Page() {
return (
<div className={themeClass}>
<div className={container}>
<button className={button}>按钮</button>
</div>
</div>
)
}
六、最佳实践 #
6.1 选择建议 #
| 场景 | 推荐 |
|---|---|
| 简单项目 | Tailwind CSS |
| 需要动态样式 | styled-components |
| 性能敏感 | vanilla-extract |
| 团队熟悉 | 选择熟悉的库 |
6.2 服务端组件 #
CSS-in-JS需要在客户端组件中使用:
tsx
'use client'
import styled from 'styled-components'
const Container = styled.div`
padding: 1rem;
`
export default function ClientComponent() {
return <Container>内容</Container>
}
6.3 组合使用 #
tsx
import { Container } from './ClientComponent'
export default async function Page() {
const data = await getData()
return (
<Container>
<h1>{data.title}</h1>
</Container>
)
}
七、总结 #
CSS-in-JS要点:
| 库 | 特点 | 适用场景 |
|---|---|---|
| styled-components | 流行,功能完整 | 动态样式 |
| emotion | 性能好,灵活 | 复杂项目 |
| stitches | 零运行时 | 性能敏感 |
| vanilla-extract | 类型安全 | 大型项目 |
下一步,让我们学习Sass与全局样式!
最后更新:2026-03-28