轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript

  • TypeScript

  • Node.js

  • Vue.js

  • 工程化

  • 浏览器与Web API

    • HTML基础-语义化标签与文档结构
    • HTML基础-文本与排版标签
    • HTML基础-列表与表格
    • HTML表单-Input类型详解
    • HTML表单-表单元素与验证
    • HTML交互-多媒体元素
    • HTML工程化-模板与组件化
    • HTML工程化-性能优化
    • CSS基础-选择器与优先级
    • CSS基础-盒模型与布局基础
    • CSS基础-单位与颜色系统
    • CSS基础-文本与字体
    • CSS基础-背景、列表与表格样式
    • CSS布局-Flexbox完全指南
    • CSS布局-Grid网格布局
    • CSS布局-响应式设计实践
    • CSS进阶-动画与过渡
    • CSS进阶-渐变与阴影效果
    • CSS进阶-Transform与3D变换
    • CSS进阶-滤镜与混合模式
    • 现代CSS-CSS预处理器对比
    • 现代CSS-CSS-in-JS方案
      • 一、CSS-in-JS概述
        • 1.1 什么是CSS-in-JS
        • 1.2 为什么需要CSS-in-JS
        • 1.3 主流方案对比
      • 二、styled-components详解
        • 2.1 styled-components简介
        • 2.2 基础使用
        • 创建样式组件
        • 基于props的样式
        • 扩展样式
        • 传递props
        • 2.3 高级特性
        • 主题(Theme)
        • 全局样式
        • CSS辅助函数
        • 动画
        • 2.4 TypeScript支持
      • 三、emotion详解
        • 3.1 emotion简介
        • 3.2 css属性方式
        • 3.3 styled方式
        • 3.4 emotion特色功能
        • 组合样式
        • 主题
        • 全局样式
        • 3.5 emotion vs styled-components
      • 四、CSS Modules详解
        • 4.1 CSS Modules简介
        • 4.2 基础使用
        • 4.3 组合样式
        • 4.4 全局样式
        • 4.5 配置
      • 五、其他方案简介
        • 5.1 JSS
        • 5.2 Linaria
        • 5.3 vanilla-extract
      • 六、方案选择指南
        • 6.1 性能对比
        • 6.2 功能对比
        • 6.3 选择建议
        • 6.4 决策流程
      • 七、最佳实践
        • 7.1 性能优化
        • 避免动态样式
        • 使用对象样式
        • 条件样式优化
        • 7.2 代码组织
        • 7.3 主题设计
        • 7.4 样式复用
        • 7.5 TypeScript最佳实践
      • 八、总结
    • 现代CSS-原子化CSS与Tailwind
    • CSS工程化-架构与规范
    • CSS工程化-性能优化
    • CSS工程化-PostCSS实战指南
    • Web API基础-DOM操作完全指南
    • Web API基础-事件处理与委托
    • Web API基础-BOM与浏览器环境
    • Web API存储-客户端存储方案
    • Web API网络-HTTP请求详解
    • Web API网络-实时通信方案
    • Web API交互-用户体验增强
    • HTML&CSS历代版本新特性
  • 前端
  • 浏览器与Web API
轩辕李
2019-09-22
目录

现代CSS-CSS-in-JS方案

CSS-in-JS是一种将样式直接写在JavaScript中的技术方案,它打破了传统的样式与逻辑分离的模式,为组件化开发带来了新的可能性。

本文将深入介绍主流的CSS-in-JS方案,包括styled-components、emotion、CSS Modules等,帮助你了解它们的特点、使用方式和适用场景,做出合适的技术选型。

# 一、CSS-in-JS概述

# 1.1 什么是CSS-in-JS

CSS-in-JS是一种在JavaScript代码中编写CSS样式的技术方案。它将样式与组件紧密结合,使样式成为组件的一部分。

传统方式:

// Button.jsx
import './Button.css';

function Button({ children }) {
  return <button className="button">{children}</button>;
}

// Button.css
.button {
  background: #667eea;
  color: white;
  padding: 10px 20px;
}

CSS-in-JS方式:

import styled from 'styled-components';

const Button = styled.button`
  background: #667eea;
  color: white;
  padding: 10px 20px;
`;

function App() {
  return <Button>Click me</Button>;
}

# 1.2 为什么需要CSS-in-JS

传统CSS的痛点:

  1. 全局命名空间:容易产生样式冲突
  2. 依赖管理困难:难以确定哪些样式可以安全删除
  3. 缺少隔离性:组件样式容易被外部影响
  4. 无法共享变量:CSS与JS之间难以共享状态
  5. 代码分割困难:样式难以按需加载

CSS-in-JS的优势:

✅ 作用域隔离:自动生成唯一类名,避免冲突 ✅ 动态样式:根据props或state动态生成样式 ✅ 死代码消除:未使用的样式自动删除 ✅ 类型安全:TypeScript支持完善 ✅ 代码分割:与组件一起按需加载 ✅ 服务端渲染:良好的SSR支持 ✅ 开发体验:热更新、自动前缀等

CSS-in-JS的劣势:

❌ 运行时开销:动态生成样式有性能成本 ❌ 包体积增加:需要引入额外的库 ❌ 学习成本:需要学习新的API ❌ 调试困难:生成的类名不直观 ❌ 首屏性能:可能影响首次渲染速度

# 1.3 主流方案对比

方案 类型 运行时 性能 流行度
styled-components 运行时 有 中等 ⭐⭐⭐⭐⭐
emotion 运行时 有 较好 ⭐⭐⭐⭐
CSS Modules 编译时 无 优秀 ⭐⭐⭐⭐⭐
JSS 运行时 有 中等 ⭐⭐⭐
Linaria 零运行时 无 优秀 ⭐⭐⭐
vanilla-extract 零运行时 无 优秀 ⭐⭐⭐

# 二、styled-components详解

# 2.1 styled-components简介

styled-components是最流行的CSS-in-JS库,使用ES6的标签模板字符串语法编写样式。

安装:

npm install styled-components
# 或
yarn add styled-components

# 2.2 基础使用

# 创建样式组件

import styled from 'styled-components';

// 基础样式组件
const Button = styled.button`
  background: #667eea;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
  
  &:hover {
    background: #5568d3;
  }
  
  &:active {
    transform: scale(0.98);
  }
`;

// 使用
function App() {
  return <Button>Click me</Button>;
}

# 基于props的样式

// 根据props动态改变样式
const Button = styled.button`
  background: ${props => props.primary ? '#667eea' : '#ecf0f1'};
  color: ${props => props.primary ? 'white' : '#2c3e50'};
  padding: ${props => props.size === 'large' ? '15px 30px' : '10px 20px'};
  border: none;
  border-radius: 4px;
  cursor: pointer;
  
  &:hover {
    opacity: 0.9;
  }
`;

// 使用
function App() {
  return (
    <>
      <Button primary>Primary Button</Button>
      <Button>Default Button</Button>
      <Button primary size="large">Large Button</Button>
    </>
  );
}

# 扩展样式

// 基础按钮
const Button = styled.button`
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
`;

// 扩展基础按钮
const PrimaryButton = styled(Button)`
  background: #667eea;
  color: white;
`;

const DangerButton = styled(Button)`
  background: #e74c3c;
  color: white;
`;

// 扩展React组件
const Link = ({ className, children, ...props }) => (
  <a className={className} {...props}>
    {children}
  </a>
);

const StyledLink = styled(Link)`
  color: #667eea;
  text-decoration: none;
  
  &:hover {
    text-decoration: underline;
  }
`;

# 传递props

// as属性:动态改变渲染元素
const Button = styled.button`
  background: #667eea;
  color: white;
  padding: 10px 20px;
`;

function App() {
  return (
    <>
      <Button>Button</Button>
      <Button as="a" href="/">Link styled as Button</Button>
      <Button as={Link} to="/">React Router Link</Button>
    </>
  );
}

// attrs:附加HTML属性
const Input = styled.input.attrs(props => ({
  type: props.type || 'text',
  placeholder: props.placeholder || 'Enter text...',
}))`
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 14px;
  
  &:focus {
    outline: none;
    border-color: #667eea;
  }
`;

# 2.3 高级特性

# 主题(Theme)

import { ThemeProvider } from 'styled-components';

// 定义主题
const lightTheme = {
  colors: {
    primary: '#667eea',
    secondary: '#764ba2',
    background: '#ffffff',
    text: '#2c3e50',
  },
  spacing: {
    small: '8px',
    medium: '16px',
    large: '24px',
  },
};

const darkTheme = {
  colors: {
    primary: '#8891f2',
    secondary: '#a78dd4',
    background: '#2c3e50',
    text: '#ffffff',
  },
  spacing: {
    small: '8px',
    medium: '16px',
    large: '24px',
  },
};

// 使用主题
const Button = styled.button`
  background: ${props => props.theme.colors.primary};
  color: white;
  padding: ${props => props.theme.spacing.medium};
  border-radius: 4px;
`;

const Container = styled.div`
  background: ${props => props.theme.colors.background};
  color: ${props => props.theme.colors.text};
  padding: ${props => props.theme.spacing.large};
`;

// 应用主题
function App() {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
      <Container>
        <Button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
          Toggle Theme
        </Button>
      </Container>
    </ThemeProvider>
  );
}

# 全局样式

import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
  
  body {
    font-family: 'Arial', sans-serif;
    background: ${props => props.theme.colors.background};
    color: ${props => props.theme.colors.text};
    line-height: 1.6;
  }
  
  a {
    color: ${props => props.theme.colors.primary};
    text-decoration: none;
  }
`;

function App() {
  return (
    <ThemeProvider theme={lightTheme}>
      <GlobalStyle />
      <Container>
        {/* 应用内容 */}
      </Container>
    </ThemeProvider>
  );
}

# CSS辅助函数

import styled, { css } from 'styled-components';

// 复用样式片段
const sharedStyles = css`
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.3s;
`;

const Button = styled.button`
  ${sharedStyles}
  background: #667eea;
  color: white;
`;

const Link = styled.a`
  ${sharedStyles}
  background: transparent;
  color: #667eea;
  border: 1px solid #667eea;
`;

// 条件样式
const Button = styled.button`
  background: #667eea;
  color: white;
  
  ${props => props.disabled && css`
    opacity: 0.6;
    cursor: not-allowed;
  `}
  
  ${props => props.size === 'large' && css`
    padding: 15px 30px;
    font-size: 18px;
  `}
`;

# 动画

import styled, { keyframes } from 'styled-components';

// 定义动画
const fadeIn = keyframes`
  from {
    opacity: 0;
    transform: translateY(-20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
`;

const rotate = keyframes`
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
`;

// 使用动画
const FadeInBox = styled.div`
  animation: ${fadeIn} 0.5s ease-in;
`;

const Spinner = styled.div`
  width: 50px;
  height: 50px;
  border: 4px solid rgba(102, 126, 234, 0.3);
  border-top-color: #667eea;
  border-radius: 50%;
  animation: ${rotate} 1s linear infinite;
`;

# 2.4 TypeScript支持

import styled from 'styled-components';

// 定义props类型
interface ButtonProps {
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
}

const Button = styled.button<ButtonProps>`
  background: ${props => props.primary ? '#667eea' : '#ecf0f1'};
  color: ${props => props.primary ? 'white' : '#2c3e50'};
  padding: ${props => {
    switch (props.size) {
      case 'small': return '8px 16px';
      case 'large': return '15px 30px';
      default: return '10px 20px';
    }
  }};
  opacity: ${props => props.disabled ? 0.6 : 1};
  cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
`;

// 主题类型
interface Theme {
  colors: {
    primary: string;
    secondary: string;
    background: string;
    text: string;
  };
  spacing: {
    small: string;
    medium: string;
    large: string;
  };
}

// 扩展styled-components的类型
declare module 'styled-components' {
  export interface DefaultTheme extends Theme {}
}

# 三、emotion详解

# 3.1 emotion简介

emotion是一个高性能的CSS-in-JS库,提供了两种使用方式:@emotion/react(框架无关)和@emotion/styled(类似styled-components)。

安装:

npm install @emotion/react @emotion/styled
# 或
yarn add @emotion/react @emotion/styled

# 3.2 css属性方式

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

// 使用css prop
function Button() {
  return (
    <button
      css={css`
        background: #667eea;
        color: white;
        padding: 10px 20px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        
        &:hover {
          background: #5568d3;
        }
      `}
    >
      Click me
    </button>
  );
}

// 对象样式
function Button() {
  return (
    <button
      css={{
        background: '#667eea',
        color: 'white',
        padding: '10px 20px',
        border: 'none',
        borderRadius: '4px',
        cursor: 'pointer',
        '&:hover': {
          background: '#5568d3',
        },
      }}
    >
      Click me
    </button>
  );
}

// 组合样式
const baseStyles = css`
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
`;

const primaryStyles = css`
  background: #667eea;
  color: white;
`;

function Button() {
  return (
    <button css={[baseStyles, primaryStyles]}>
      Click me
    </button>
  );
}

# 3.3 styled方式

import styled from '@emotion/styled';

// 基础用法(与styled-components相同)
const Button = styled.button`
  background: #667eea;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  
  &:hover {
    background: #5568d3;
  }
`;

// 对象样式
const Button = styled.button({
  background: '#667eea',
  color: 'white',
  padding: '10px 20px',
  border: 'none',
  borderRadius: '4px',
  cursor: 'pointer',
  '&:hover': {
    background: '#5568d3',
  },
});

// 动态样式
const Button = styled.button`
  background: ${props => props.primary ? '#667eea' : '#ecf0f1'};
  color: ${props => props.primary ? 'white' : '#2c3e50'};
  padding: 10px 20px;
`;

# 3.4 emotion特色功能

# 组合样式

import { css } from '@emotion/react';

const baseStyles = css`
  padding: 10px 20px;
  border-radius: 4px;
`;

const primaryStyles = css`
  ${baseStyles}
  background: #667eea;
  color: white;
`;

const dangerStyles = css`
  ${baseStyles}
  background: #e74c3c;
  color: white;
`;

# 主题

import { ThemeProvider } from '@emotion/react';

const theme = {
  colors: {
    primary: '#667eea',
    secondary: '#764ba2',
  },
};

function Button() {
  return (
    <button
      css={theme => ({
        background: theme.colors.primary,
        color: 'white',
        padding: '10px 20px',
      })}
    >
      Click me
    </button>
  );
}

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button />
    </ThemeProvider>
  );
}

# 全局样式

import { Global, css } from '@emotion/react';

function App() {
  return (
    <>
      <Global
        styles={css`
          * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
          }
          
          body {
            font-family: Arial, sans-serif;
            line-height: 1.6;
          }
        `}
      />
      {/* 应用内容 */}
    </>
  );
}

# 3.5 emotion vs styled-components

特性 emotion styled-components
包大小 更小 (7.9kB) 较大 (15.5kB)
性能 更快 较快
API 更灵活 更简单
css prop ✅ 原生支持 ❌ 需要插件
对象样式 ✅ 完善支持 ⚠️ 部分支持
SSR ✅ 优秀 ✅ 良好
社区 较大 最大
TypeScript ✅ 优秀 ✅ 优秀

选择建议:

  • emotion:追求性能和灵活性
  • styled-components:追求稳定性和社区支持

# 四、CSS Modules详解

# 4.1 CSS Modules简介

CSS Modules是一种编译时的CSS-in-JS方案,通过构建工具自动生成唯一的类名,实现样式隔离。

特点:

  • 零运行时开销
  • 完全的CSS语法
  • 自动作用域隔离
  • 支持CSS预处理器

# 4.2 基础使用

/* Button.module.css */
.button {
  background: #667eea;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.button:hover {
  background: #5568d3;
}

.primary {
  background: #667eea;
}

.secondary {
  background: #764ba2;
}

.large {
  padding: 15px 30px;
  font-size: 18px;
}
// Button.jsx
import styles from './Button.module.css';

function Button({ primary, large, children }) {
  return (
    <button 
      className={`
        ${styles.button} 
        ${primary ? styles.primary : ''} 
        ${large ? styles.large : ''}
      `}
    >
      {children}
    </button>
  );
}

// 使用classnames库简化
import classNames from 'classnames';

function Button({ primary, large, children }) {
  return (
    <button 
      className={classNames(
        styles.button,
        { [styles.primary]: primary },
        { [styles.large]: large }
      )}
    >
      {children}
    </button>
  );
}

# 4.3 组合样式

/* common.module.css */
.button {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

/* Button.module.css */
.primary {
  composes: button from './common.module.css';
  background: #667eea;
  color: white;
}

.secondary {
  composes: button from './common.module.css';
  background: #764ba2;
  color: white;
}

/* 组合多个类 */
.dangerButton {
  composes: button large from './common.module.css';
  background: #e74c3c;
  color: white;
}

# 4.4 全局样式

/* 全局样式 */
:global(.app-container) {
  max-width: 1200px;
  margin: 0 auto;
}

/* 局部和全局混合 */
.wrapper :global(.external-class) {
  color: red;
}

/* 切换到全局模式 */
:global {
  .reset * {
    margin: 0;
    padding: 0;
  }
}

# 4.5 配置

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.module\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: {
                localIdentName: '[name]__[local]--[hash:base64:5]',
              },
            },
          },
        ],
      },
    ],
  },
};

// vite.config.js
export default {
  css: {
    modules: {
      localsConvention: 'camelCase',
      generateScopedName: '[name]__[local]--[hash:base64:5]',
    },
  },
};

# 五、其他方案简介

# 5.1 JSS

import { createUseStyles } from 'react-jss';

const useStyles = createUseStyles({
  button: {
    background: '#667eea',
    color: 'white',
    padding: '10px 20px',
    border: 'none',
    borderRadius: '4px',
    cursor: 'pointer',
    '&:hover': {
      background: '#5568d3',
    },
  },
  primary: {
    background: props => props.primary ? '#667eea' : '#ecf0f1',
  },
});

function Button(props) {
  const classes = useStyles(props);
  return (
    <button className={classes.button}>
      {props.children}
    </button>
  );
}

# 5.2 Linaria

零运行时的CSS-in-JS方案,在构建时提取CSS:

import { css } from '@linaria/core';
import { styled } from '@linaria/react';

// css函数
const title = css`
  font-size: 24px;
  color: #2c3e50;
`;

// styled API
const Button = styled.button`
  background: #667eea;
  color: white;
  padding: 10px 20px;
  border-radius: 4px;
`;

function App() {
  return (
    <>
      <h1 className={title}>Hello</h1>
      <Button>Click me</Button>
    </>
  );
}

# 5.3 vanilla-extract

类型安全的零运行时CSS-in-JS:

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

export const button = style({
  background: '#667eea',
  color: 'white',
  padding: '10px 20px',
  borderRadius: '4px',
  selectors: {
    '&:hover': {
      background: '#5568d3',
    },
  },
});

// Button.tsx
import * as styles from './styles.css';

export function Button() {
  return (
    <button className={styles.button}>
      Click me
    </button>
  );
}

# 六、方案选择指南

# 6.1 性能对比

方案 运行时 包大小 首屏性能 运行性能
CSS Modules 无 0KB ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
Linaria 无 ~5KB ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
vanilla-extract 无 ~5KB ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
emotion 有 ~8KB ⭐⭐⭐⭐ ⭐⭐⭐⭐
styled-components 有 ~16KB ⭐⭐⭐ ⭐⭐⭐
JSS 有 ~15KB ⭐⭐⭐ ⭐⭐⭐

# 6.2 功能对比

功能 CSS Modules emotion styled-components
作用域隔离 ✅ ✅ ✅
动态样式 ❌ ✅ ✅
主题支持 ⚠️ 需额外配置 ✅ ✅
SSR ✅ ✅ ✅
代码分割 ✅ ✅ ✅
TypeScript ✅ ✅ ✅
开发体验 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
学习成本 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐

# 6.3 选择建议

选择CSS Modules:

  • ✅ 追求极致性能
  • ✅ 不需要动态样式
  • ✅ 团队熟悉传统CSS
  • ✅ 需要使用CSS预处理器

选择emotion:

  • ✅ 需要动态样式
  • ✅ 追求灵活性
  • ✅ 看重性能和包大小
  • ✅ 需要css prop功能

选择styled-components:

  • ✅ 需要动态样式
  • ✅ 追求开发体验
  • ✅ 看重社区和生态
  • ✅ 团队已有经验

选择零运行时方案(Linaria/vanilla-extract):

  • ✅ 追求零运行时开销
  • ✅ 需要动态样式
  • ✅ 愿意接受构建配置

# 6.4 决策流程

需要动态样式吗?
├─ 否 → CSS Modules
└─ 是 → 继续

性能是否关键?
├─ 是 → Linaria 或 vanilla-extract
└─ 否 → 继续

更看重什么?
├─ 性能和灵活性 → emotion
├─ 社区和稳定性 → styled-components
└─ 类型安全 → vanilla-extract

# 七、最佳实践

# 7.1 性能优化

# 避免动态样式

// ❌ 避免:每次渲染都创建新样式
function Button() {
  return (
    <button
      css={css`
        background: #667eea;
        padding: 10px 20px;
      `}
    >
      Click
    </button>
  );
}

// ✅ 推荐:提取到组件外部
const buttonStyles = css`
  background: #667eea;
  padding: 10px 20px;
`;

function Button() {
  return <button css={buttonStyles}>Click</button>;
}

# 使用对象样式

// 对象样式有更好的缓存
const buttonStyles = {
  background: '#667eea',
  padding: '10px 20px',
  borderRadius: '4px',
};

function Button() {
  return <button css={buttonStyles}>Click</button>;
}

# 条件样式优化

// ❌ 避免:内联条件
<button css={{ background: isActive ? 'blue' : 'gray' }}>

// ✅ 推荐:预定义样式
const baseStyle = { padding: '10px' };
const activeStyle = { background: 'blue' };
const inactiveStyle = { background: 'gray' };

<button css={[baseStyle, isActive ? activeStyle : inactiveStyle]}>

# 7.2 代码组织

src/
├── styles/
│   ├── theme.ts          # 主题配置
│   ├── global.ts         # 全局样式
│   ├── mixins.ts         # 样式混合
│   └── tokens.ts         # 设计令牌
├── components/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.styles.ts  # 样式文件
│   │   └── index.ts
│   └── Card/
│       ├── Card.tsx
│       └── Card.styles.ts

# 7.3 主题设计

// theme.ts
export const theme = {
  colors: {
    primary: {
      main: '#667eea',
      light: '#8891f2',
      dark: '#5568d3',
      contrastText: '#ffffff',
    },
    secondary: {
      main: '#764ba2',
      light: '#a78dd4',
      dark: '#5a3877',
      contrastText: '#ffffff',
    },
    background: {
      default: '#ffffff',
      paper: '#f5f5f5',
    },
    text: {
      primary: '#2c3e50',
      secondary: '#7f8c8d',
      disabled: '#bdc3c7',
    },
  },
  spacing: (factor: number) => `${8 * factor}px`,
  typography: {
    fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
    fontSize: {
      small: '12px',
      medium: '14px',
      large: '16px',
      xlarge: '20px',
    },
    fontWeight: {
      light: 300,
      regular: 400,
      medium: 500,
      bold: 700,
    },
  },
  shadows: {
    small: '0 2px 4px rgba(0,0,0,0.1)',
    medium: '0 4px 8px rgba(0,0,0,0.1)',
    large: '0 8px 16px rgba(0,0,0,0.1)',
  },
  borderRadius: {
    small: '4px',
    medium: '8px',
    large: '12px',
    round: '50%',
  },
  transitions: {
    duration: {
      shortest: 150,
      shorter: 200,
      short: 250,
      standard: 300,
      complex: 375,
    },
    easing: {
      easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
      easeOut: 'cubic-bezier(0.0, 0, 0.2, 1)',
      easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
      sharp: 'cubic-bezier(0.4, 0, 0.6, 1)',
    },
  },
  breakpoints: {
    xs: '0px',
    sm: '576px',
    md: '768px',
    lg: '992px',
    xl: '1200px',
  },
};

export type Theme = typeof theme;

# 7.4 样式复用

// mixins.ts
import { css } from '@emotion/react';

export const flexCenter = css`
  display: flex;
  justify-content: center;
  align-items: center;
`;

export const truncateText = css`
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
`;

export const hideScrollbar = css`
  -ms-overflow-style: none;
  scrollbar-width: none;
  
  &::-webkit-scrollbar {
    display: none;
  }
`;

export const visuallyHidden = css`
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
`;

# 7.5 TypeScript最佳实践

// 定义组件props
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'text';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  fullWidth?: boolean;
  children: React.ReactNode;
}

// 定义样式props
interface StyledButtonProps {
  $variant: ButtonProps['variant'];
  $size: ButtonProps['size'];
  $disabled: boolean;
  $fullWidth: boolean;
}

// 使用$前缀避免props传递到DOM
const StyledButton = styled.button<StyledButtonProps>`
  background: ${props => {
    switch (props.$variant) {
      case 'primary': return props.theme.colors.primary.main;
      case 'secondary': return props.theme.colors.secondary.main;
      default: return 'transparent';
    }
  }};
  
  padding: ${props => {
    switch (props.$size) {
      case 'small': return props.theme.spacing(1);
      case 'large': return props.theme.spacing(3);
      default: return props.theme.spacing(2);
    }
  }};
  
  width: ${props => props.$fullWidth ? '100%' : 'auto'};
  opacity: ${props => props.$disabled ? 0.6 : 1};
  cursor: ${props => props.$disabled ? 'not-allowed' : 'pointer'};
`;

export function Button({
  variant = 'primary',
  size = 'medium',
  disabled = false,
  fullWidth = false,
  children,
}: ButtonProps) {
  return (
    <StyledButton
      $variant={variant}
      $size={size}
      $disabled={disabled}
      $fullWidth={fullWidth}
      disabled={disabled}
    >
      {children}
    </StyledButton>
  );
}

# 八、总结

CSS-in-JS为现代Web开发提供了新的样式解决方案:

主流方案特点:

  • styled-components:最成熟、社区最大、开发体验最好
  • emotion:性能更好、更灵活、包体积更小
  • CSS Modules:零运行时、性能最优、学习成本低
  • 零运行时方案:结合两者优势,但配置较复杂

选择建议:

  1. 追求性能 → CSS Modules 或零运行时方案
  2. 需要动态样式 → emotion 或 styled-components
  3. 大型项目 → styled-components(生态成熟)
  4. 中小型项目 → emotion(灵活轻量)
  5. 简单项目 → CSS Modules(简单直接)

最佳实践:

  • 合理组织代码结构
  • 提取样式到组件外部
  • 使用主题系统
  • 复用样式片段
  • 注意性能优化
  • 善用TypeScript

无论选择哪种方案,都要:

  • 理解其原理和特点
  • 遵循最佳实践
  • 注重性能和可维护性
  • 根据项目实际需求选择

祝你变得更强!

编辑 (opens new window)
#CSS#CSS-in-JS#styled-components#emotion
上次更新: 2025/11/20
现代CSS-CSS预处理器对比
现代CSS-原子化CSS与Tailwind

← 现代CSS-CSS预处理器对比 现代CSS-原子化CSS与Tailwind→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式