Skip to content
📖预计阅读时长:0 分钟字数:0

话题 3:技术选型对比与取舍

本文是「设计系统与 Tailwind CSS 深度实践」系列的第三篇,深度对比各种 CSS 方案的优劣,并给出明确的选型建议。


一、CSS 方案全景图

                        ┌─────────────────────────────────────┐
                        │           CSS 方案演进               │
                        └─────────────────────────────────────┘

        ┌────────────────┬───────────────┼───────────────┬────────────────┐
        ▼                ▼               ▼               ▼                ▼
   ┌─────────┐    ┌───────────┐   ┌───────────┐   ┌───────────┐   ┌───────────┐
   │ 传统CSS  │    │ CSS预处理 │   │CSS Modules│   │ CSS-in-JS │   │ Utility   │
   │ + BEM   │    │SCSS/Less  │   │  Scoped   │   │ Runtime   │   │  First    │
   └─────────┘    └───────────┘   └───────────┘   └───────────┘   └───────────┘
        │                │               │               │                │
        │                │               │               │                │
   命名约定          预处理能力       作用域隔离      JS 动态能力      原子化思想
   手动维护          变量/嵌套       构建时生成       运行时生成       约束式设计

                                                    ┌────────────────────┼────────────────────┐
                                                    ▼                    ▼                    ▼
                                               ┌─────────┐         ┌─────────┐         ┌─────────┐
                                               │Tailwind │         │ UnoCSS  │         │PandaCSS │
                                               │  CSS    │         │         │         │         │
                                               └─────────┘         └─────────┘         └─────────┘

二、主流方案深度对比

2.1 对比维度说明

维度含义
DX(开发体验)编写效率、IDE 支持、调试便利性
性能构建产物大小、运行时开销、首屏加载
主题切换暗黑模式、多品牌切换的实现难度
类型安全TypeScript 支持程度
维护性长期维护、重构、团队协作成本
学习曲线上手难度

2.2 全方位对比表

方案DX性能主题切换类型安全维护性学习曲线
Tailwind CSS⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
CSS Modules⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Styled-components⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Emotion⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Panda CSS⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Vanilla Extract⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
UnoCSS⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
SCSS + BEM⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

三、逐一深度分析

3.1 Tailwind CSS

定位:Utility-First CSS 框架,构建时生成

jsx
// 使用示例
<button className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md">
  Click me
</button>

优势

  • 极致的开发效率(不用想 class 名)
  • 零运行时,CSS 在构建时生成
  • PurgeCSS 自动删除未使用样式,产物极小
  • 强大的响应式和状态变体(md:, hover:, dark:
  • 生态成熟,社区活跃

劣势

  • class 字符串较长(通过组件封装解决)
  • 动态样式需要完整 class 名(无法用字符串拼接)
  • TypeScript 支持一般(class 名是字符串)

动态样式的陷阱

jsx
// ❌ 错误:Tailwind 无法识别动态拼接的 class
const color = 'blue';
<div className={`bg-${color}-500`} />  // 不会生成对应 CSS

// ✅ 正确:使用完整的 class 名
const colorClasses = {
  blue: 'bg-blue-500',
  red: 'bg-red-500',
};
<div className={colorClasses[color]} />

主题切换实现

jsx
// 通过 CSS 变量 + class 切换
<html className="dark">
  <body className="bg-background text-foreground">
    {/* 自动应用暗黑主题 */}
  </body>
</html>

3.2 CSS Modules

定位:CSS 作用域隔离方案,构建时生成唯一 class 名

css
/* Button.module.css */
.button {
  background: var(--color-primary);
  padding: 8px 16px;
  border-radius: 8px;
}
.button:hover {
  background: var(--color-primary-hover);
}
jsx
// Button.jsx
import styles from './Button.module.css';

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

优势

  • 学习成本最低(就是写 CSS)
  • 天然的作用域隔离
  • 零运行时开销
  • 与任何框架兼容

劣势

  • 需要单独的 CSS 文件
  • 动态样式不如 CSS-in-JS 灵活
  • 主题切换依赖 CSS 变量(但这其实是好事)
  • 缺乏类型安全

主题切换实现

css
/* 依赖 CSS 变量 */
.button {
  background: var(--color-primary);
  color: var(--color-primary-foreground);
}

3.3 Styled-components / Emotion(CSS-in-JS Runtime)

定位:在 JS 中编写 CSS,运行时生成

jsx
// styled-components 示例
import styled from 'styled-components';

const Button = styled.button`
  background: ${props => props.theme.colors.primary};
  color: white;
  padding: 8px 16px;
  border-radius: 8px;
  
  &:hover {
    background: ${props => props.theme.colors.primaryHover};
  }
`;

// 使用
<ThemeProvider theme={lightTheme}>
  <Button>Click me</Button>
</ThemeProvider>

优势

  • 动态样式极其灵活(直接用 JS 变量)
  • 主题切换体验最好(ThemeProvider)
  • TypeScript 支持好
  • 样式与组件天然绑定

劣势

  • 运行时开销:每次渲染都要计算样式
  • SSR 复杂:需要额外配置样式提取
  • 包体积:runtime 本身有大小
  • React 19 兼容性问题:styled-components 与 React 19 存在已知问题

性能问题详解

jsx
// 每次 props 变化都会重新计算样式并注入
const Box = styled.div`
  color: ${props => props.color};  // 运行时计算
`;

// 大量动态样式会导致:
// 1. JS 执行时间增加
// 2. 样式表不断增长
// 3. 可能触发 FOUC(Flash of Unstyled Content)

我的明确观点2024 年后,不推荐新项目使用 Runtime CSS-in-JS

原因:

  1. React Server Components 不支持运行时 CSS
  2. 性能问题在大型应用中会累积
  3. 有更好的零运行时替代方案

3.4 Panda CSS(零运行时 CSS-in-JS)

定位:Chakra UI 团队出品,结合 CSS-in-JS 的 DX 和零运行时性能

jsx
// panda.config.ts
import { defineConfig } from '@pandacss/dev';

export default defineConfig({
  theme: {
    tokens: {
      colors: {
        primary: { value: '#3B82F6' },
        secondary: { value: '#6B7280' },
      }
    },
    semanticTokens: {
      colors: {
        background: {
          value: { base: '#FFFFFF', _dark: '#0F172A' }
        }
      }
    }
  }
});
jsx
// 使用方式一:css 函数
import { css } from '../styled-system/css';

function Button() {
  return (
    <button className={css({
      bg: 'primary',
      color: 'white',
      px: '4',
      py: '2',
      borderRadius: 'md',
      _hover: { bg: 'blue.600' }
    })}>
      Click me
    </button>
  );
}

// 使用方式二:styled API(类似 styled-components)
import { styled } from '../styled-system/jsx';

const Button = styled('button', {
  base: {
    bg: 'primary',
    color: 'white',
    px: '4',
    py: '2',
  },
  variants: {
    size: {
      sm: { px: '2', py: '1', fontSize: 'sm' },
      lg: { px: '6', py: '3', fontSize: 'lg' },
    }
  }
});

优势

  • 零运行时:构建时提取为静态 CSS
  • 类型安全:完整的 TypeScript 支持
  • Token 原生支持:内置 Token 分层概念
  • 多主题友好:semantic tokens 原生支持 _dark
  • RSC 兼容:支持 React Server Components

劣势

  • 相对较新,生态不如 Tailwind 成熟
  • 需要构建步骤(codegen)
  • 配置稍复杂

与 Tailwind 的关键区别

方面TailwindPanda CSS
编写方式class 字符串JS 对象
类型安全
动态样式需要 safelist自然支持
Token 定义config.jsconfig.ts(类型安全)

3.5 Vanilla Extract

定位:TypeScript 优先的零运行时 CSS

typescript
// button.css.ts
import { style, createTheme } from '@vanilla-extract/css';

export const [themeClass, vars] = createTheme({
  color: {
    primary: '#3B82F6',
    background: '#FFFFFF',
  }
});

export const button = style({
  backgroundColor: vars.color.primary,
  color: 'white',
  padding: '8px 16px',
  borderRadius: '8px',
  ':hover': {
    backgroundColor: '#2563EB',
  }
});
jsx
// Button.tsx
import { button, themeClass } from './button.css';

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

优势

  • 极致类型安全:样式定义就是 TypeScript
  • 零运行时:构建时生成静态 CSS
  • 主题系统强大createTheme 原生支持多主题
  • 局部作用域:自动生成唯一 class 名

劣势

  • 学习曲线陡峭
  • 需要单独的 .css.ts 文件
  • DX 不如 Tailwind(需要写更多代码)
  • 构建配置复杂

3.6 UnoCSS

定位:原子化 CSS 引擎,Tailwind 的高性能替代

jsx
// 与 Tailwind 语法兼容
<button className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md">
  Click me
</button>

// 也支持 Attributify 模式
<button bg="blue-500 hover:blue-600" text="white" p="x-4 y-2" rounded="md">
  Click me
</button>

优势

  • 极快的构建速度(比 Tailwind 快 5-100 倍)
  • 完全可定制的预设系统
  • 支持多种语法模式(Tailwind / Windi / Attributify)
  • 按需生成,零配置即可使用

劣势

  • 生态不如 Tailwind(组件库、插件等)
  • 文档相对较少
  • 部分边缘情况与 Tailwind 有差异

何时选 UnoCSS

  • 需要极致构建性能
  • 喜欢 Attributify 语法
  • 项目不依赖 Tailwind 生态(如 shadcn/ui)

3.7 SCSS + BEM(传统方案)

scss
// components/Button/_button.scss
.button {
  padding: 8px 16px;
  border-radius: 8px;
  
  &--primary {
    background: $color-primary;
    color: white;
    
    &:hover {
      background: $color-primary-dark;
    }
  }
  
  &--secondary {
    background: $color-gray-100;
    color: $color-gray-900;
  }
  
  &__icon {
    margin-right: 8px;
  }
}

优势

  • 成熟稳定,团队熟悉
  • 无框架依赖
  • 调试友好(class 名可读)

劣势

  • 需要手动维护命名规范
  • 全局作用域,容易冲突
  • 主题切换实现繁琐
  • 容易产生死代码

我的观点:SCSS + BEM 是"可用但不推荐"的方案。对于新的组件库项目,有更好的选择。


四、决策矩阵:如何选择?

4.1 按场景推荐

场景首选备选不推荐
个人组件库TailwindPanda CSSRuntime CSS-in-JS
企业级组件库Panda CSS / Vanilla ExtractTailwindSCSS + BEM
快速原型TailwindUnoCSSVanilla Extract
需要极致类型安全Vanilla ExtractPanda CSSTailwind
需要用 shadcn/uiTailwind--
已有 SCSS 代码库CSS Modules + 渐进迁移--
Next.js App RouterTailwind / Panda CSSCSS Modulesstyled-components

4.2 你的场景分析

根据你的背景:

  • 个人组件库 + 个人项目
  • React 技术栈
  • 有多主题需求
  • 中级前端水平

我的明确推荐:Tailwind CSS

理由:

  1. 生态最成熟:可以直接使用 shadcn/ui、Radix UI 等优秀的无样式/Tailwind 组件库
  2. 学习曲线适中:你已有 Tailwind 基础,深入不难
  3. 主题切换方案成熟:CSS 变量 + dark class,前两篇已详述
  4. 社区资源丰富:遇到问题容易找到解决方案
  5. 与 Design Token 理念兼容:config 本身就是 Token 层

备选考虑

如果将来你需要:

  • 更强的类型安全 → 考虑 Panda CSS
  • 更灵活的动态样式 → 考虑 Panda CSS
  • 团队扩大,需要更严格的约束 → 考虑 Vanilla Extract

五、混合使用策略

5.1 Tailwind + CSS Modules 混用

jsx
// 复杂动画或特殊样式用 CSS Modules
import styles from './ComplexAnimation.module.css';

function Card() {
  return (
    // 基础样式用 Tailwind,特殊效果用 CSS Modules
    <div className={`bg-background rounded-lg p-4 ${styles.cardHover}`}>
      {/* ... */}
    </div>
  );
}
css
/* ComplexAnimation.module.css */
.cardHover {
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.cardHover:hover {
  transform: translateY(-4px) scale(1.02);
  box-shadow: 
    0 20px 25px -5px rgb(0 0 0 / 0.1),
    0 8px 10px -6px rgb(0 0 0 / 0.1);
}

5.2 什么时候需要混用?

场景用 Tailwind用 CSS Modules
常规布局、颜色、间距
复杂动画序列
伪元素内容(::before/::after)
复杂选择器(:nth-child 等)
第三方组件样式覆盖

六、本轮小结

核心观点

  1. Runtime CSS-in-JS 正在衰落:styled-components/Emotion 在 RSC 时代面临挑战
  2. 零运行时是趋势:Tailwind、Panda CSS、Vanilla Extract 都是零运行时
  3. 选型要看场景:没有银弹,但有最佳匹配
  4. Tailwind 对你最合适:生态、学习曲线、主题支持都是优势
  5. 混用是合理的:Tailwind 处理 80% 场景,CSS Modules 处理特殊需求

方案对比速查

选型决策树:

需要极致类型安全?
├─ 是 → Vanilla Extract 或 Panda CSS
└─ 否 → 继续

需要动态样式灵活性?
├─ 是 → Panda CSS
└─ 否 → 继续

需要 shadcn/ui 等 Tailwind 生态?
├─ 是 → Tailwind CSS
└─ 否 → 继续

追求极致构建速度?
├─ 是 → UnoCSS
└─ 否 → Tailwind CSS

本轮思考问题

在进入下一个话题(多主题/暗黑模式最佳实践)之前:

  1. 你之前有没有用过 CSS-in-JS(styled-components/Emotion)? 如果用过,体验如何?

  2. 你的组件库是否会用到 shadcn/ui 或类似的组件库? 这会影响技术选型。

  3. 对于 Tailwind 的"class 较长"问题,你觉得是问题吗? 还是说通过组件封装后可以接受?