iT邦幫忙

2025 iThome 鐵人賽

DAY 0
2
Modern Web

前端工程師的 Modern Web 實踐之道系列 第 6

CSS 進化史:從 BEM 到 CSS-in-JS 到 Tailwind 的現代化選擇

  • 分享至 

  • xImage
  •  

系列文章: 前端工程師的 Modern Web 實踐之道 - Day 6
預計閱讀時間: 10 分鐘
難度等級: ⭐⭐⭐☆☆

🎯 今日目標

在前一篇文章中,我們探討了框架選擇的藝術。今天我們將深入探討 CSS 的現代化演進,從經典的 BEM 方法論到 CSS-in-JS,再到近年來備受關注的 Tailwind CSS。這個主題將幫助你建立系統性的 CSS 架構思維,在不同專案中做出最適合的技術選擇。

為什麼要關注 CSS 的現代化選擇?

  • 維護成本: 不良的 CSS 架構會讓專案維護成本呈指數增長
  • 開發效率: 現代化的 CSS 方案能顯著提升開發速度和團隊協作
  • 效能最佳化: 不同方案對最終 bundle 大小和執行時效能影響巨大
  • 團隊協作: 統一的 CSS 架構策略是大型團隊協作的基礎

🔍 深度分析:CSS 架構演進的技術脈絡

問題背景:CSS 為何需要架構化?

還記得那些年我們寫 CSS 的痛苦嗎?全域污染、樣式衝突、難以維護的巨大 CSS 檔案。隨著 Web 應用複雜度的提升,這些問題變得更加嚴重:

/* 傳統 CSS 的痛點範例 */
.header { background: blue; }
.content .header { background: red; } /* 意外覆蓋 */
.user-profile .header { background: green; } /* 更深層的覆蓋 */

/* 樣式越來越複雜,維護成本呈指數增長 */
.header .nav .menu .item .link:hover:not(.disabled):first-child {
  /* 這樣的選擇器在大型專案中隨處可見 */
}

CSS 架構方案的三個演進階段

階段一:方法論時代 (BEM, OOCSS, SMACSS)

核心思想是透過命名約定和組織規範來解決 CSS 混亂問題。

階段二:工具化時代 (Sass, Less, PostCSS)

引入預處理器和後處理器,增強 CSS 的程式化能力。

階段三:現代化時代 (CSS-in-JS, Tailwind, CSS Modules)

將 CSS 與 JavaScript 生態系統深度整合,實現更智能的樣式管理。

💻 方案深度對比與實戰分析

BEM 方法論:經典且持久的解決方案

BEM (Block Element Modifier) 是一套命名約定,透過結構化的類別名稱來避免樣式衝突。

核心概念與實作

// BEM 結構範例
.card {                    // Block
  padding: 1rem;
  border-radius: 8px;
  background: white;

  &__header {              // Element
    font-size: 1.2rem;
    font-weight: bold;
    margin-bottom: 0.5rem;
  }

  &__content {             // Element
    line-height: 1.6;
  }

  &--featured {            // Modifier
    border: 2px solid gold;
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
  }

  &--large {               // Modifier
    padding: 2rem;

    .card__header {
      font-size: 1.5rem;
    }
  }
}

React 元件中的 BEM 實踐

const Card = ({ title, content, featured = false, size = 'normal' }) => {
  const cardClass = classNames('card', {
    'card--featured': featured,
    'card--large': size === 'large'
  });

  return (
    <div className={cardClass}>
      <h3 className="card__header">{title}</h3>
      <div className="card__content">{content}</div>
    </div>
  );
};

BEM 的優勢與限制

  • ✅ 語義清晰,容易理解和維護
  • ✅ 避免樣式衝突,選擇器權重扁平
  • ✅ 與任何框架或原生 HTML 相容
  • ❌ 類別名稱冗長,HTML 檔案變大
  • ❌ 缺乏動態樣式支援
  • ❌ 需要嚴格的命名紀律

CSS-in-JS:組件化時代的樣式解決方案

CSS-in-JS 將樣式邏輯直接整合到 JavaScript 中,實現真正的組件化封裝。

Styled Components 實戰範例

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

// 基礎樣式組件
const CardContainer = styled.div`
  padding: 1rem;
  border-radius: 8px;
  background: white;
  transition: all 0.3s ease;

  ${props => props.featured && css`
    border: 2px solid gold;
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
  `}

  ${props => props.size === 'large' && css`
    padding: 2rem;
  `}

  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 16px rgba(0,0,0,0.15);
  }
`;

const CardHeader = styled.h3`
  font-size: ${props => props.size === 'large' ? '1.5rem' : '1.2rem'};
  font-weight: bold;
  margin-bottom: 0.5rem;
  color: ${props => props.theme.colors.primary};
`;

// 動態樣式與主題支援
const Card = ({ title, content, featured, size, theme }) => (
  <ThemeProvider theme={theme}>
    <CardContainer featured={featured} size={size}>
      <CardHeader size={size}>{title}</CardHeader>
      <div>{content}</div>
    </CardContainer>
  </ThemeProvider>
);

進階應用:動態樣式與效能最佳化

// 複雜的動態樣式邏輯
const DynamicButton = styled.button`
  padding: ${props => {
    const sizeMap = {
      small: '0.5rem 1rem',
      medium: '0.75rem 1.5rem',
      large: '1rem 2rem'
    };
    return sizeMap[props.size] || sizeMap.medium;
  }};

  background: ${props => {
    if (props.variant === 'primary') return props.theme.colors.primary;
    if (props.variant === 'secondary') return props.theme.colors.secondary;
    return 'transparent';
  }};

  // 條件式樣式避免不必要的 CSS 生成
  ${props => props.loading && css`
    position: relative;
    color: transparent;

    &::after {
      content: '';
      position: absolute;
      width: 16px;
      height: 16px;
      top: 50%;
      left: 50%;
      margin-left: -8px;
      margin-top: -8px;
      border: 2px solid currentColor;
      border-radius: 50%;
      border-right-color: transparent;
      animation: spin 1s linear infinite;
    }
  `}
`;

CSS-in-JS 的優勢與挑戰

  • ✅ 真正的組件封裝,樣式與邏輯統一管理
  • ✅ 動態樣式支援,可以基於 props 和 state 變化
  • ✅ 主題系統和設計系統整合性強
  • ✅ 死碼消除,只會包含實際使用的樣式
  • ❌ 執行時開銷,需要 JavaScript 解析
  • ❌ 學習成本高,需要理解新的開發模式
  • ❌ SSR 複雜度增加
  • ❌ 開發者工具支援有限

Tailwind CSS:功能導向的現代化選擇

Tailwind CSS 提供了一套低階的功能類別,讓你可以快速建構客製化設計。

核心理念與基礎使用

<!-- 傳統方式 -->
<div class="card card--featured card--large">
  <h3 class="card__header">標題</h3>
  <p class="card__content">內容</p>
</div>

<!-- Tailwind 方式 -->
<div class="bg-white rounded-lg p-6 shadow-lg border-2 border-yellow-400 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
  <h3 class="text-xl font-bold mb-2 text-gray-800">標題</h3>
  <p class="text-gray-600 leading-relaxed">內容</p>
</div>

React 組件中的 Tailwind 最佳實踐

// 使用 clsx 進行條件式類別組合
import clsx from 'clsx';

const Card = ({
  title,
  content,
  featured = false,
  size = 'normal',
  className = ''
}) => {
  const cardClasses = clsx(
    // 基礎樣式
    'bg-white rounded-lg shadow-lg transition-all duration-300 hover:shadow-xl hover:-translate-y-1',
    // 大小變化
    {
      'p-4': size === 'normal',
      'p-6': size === 'large',
    },
    // 特殊狀態
    {
      'border-2 border-yellow-400': featured,
      'border border-gray-200': !featured,
    },
    // 自定義 className
    className
  );

  const headerClasses = clsx(
    'font-bold mb-2 text-gray-800',
    {
      'text-lg': size === 'normal',
      'text-xl': size === 'large',
    }
  );

  return (
    <div className={cardClasses}>
      <h3 className={headerClasses}>{title}</h3>
      <div className="text-gray-600 leading-relaxed">{content}</div>
    </div>
  );
};

進階技巧:自定義組件與設計系統

// tailwind.config.js - 建立設計系統
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#f0f9ff',
          500: '#3b82f6',
          900: '#1e3a8a',
        }
      },
      spacing: {
        '18': '4.5rem',
        '88': '22rem',
      },
      animation: {
        'fade-in-up': 'fadeInUp 0.5s ease-out',
      }
    }
  },
  plugins: [
    // 自定義功能類別
    function({ addComponents }) {
      addComponents({
        '.btn-primary': {
          '@apply bg-brand-500 text-white px-6 py-3 rounded-lg font-semibold hover:bg-brand-600 transition-colors duration-200': {},
        },
        '.card': {
          '@apply bg-white rounded-lg shadow-lg p-6 border border-gray-200': {},
        }
      })
    }
  ]
}

Tailwind CSS 的特色與考量

  • ✅ 極快的開發速度,不需要寫自定義 CSS
  • ✅ 一致的設計系統,避免魔法數字
  • ✅ 優秀的 tree-shaking,最終 bundle 很小
  • ✅ 響應式設計和暗黑模式支援完善
  • ❌ HTML 檔案會變得冗長
  • ❌ 學習成本,需要記憶大量的功能類別
  • ❌ 自定義設計的彈性有限
  • ❌ 團隊成員需要統一的開發習慣

🎯 實際專案選擇策略

專案特性與技術選擇對應表

專案特性 BEM CSS-in-JS Tailwind
小型專案(< 10 頁面) ⭐⭐⭐ ⭐⭐ ⭐⭐⭐
中型專案(10-50 頁面) ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐
大型專案(> 50 頁面) ⭐⭐ ⭐⭐⭐ ⭐⭐
設計系統重要性 ⭐⭐ ⭐⭐⭐ ⭐⭐⭐
開發速度要求 ⭐⭐ ⭐⭐ ⭐⭐⭐
客製化需求 ⭐⭐⭐ ⭐⭐⭐ ⭐⭐
團隊技術水平 ⭐⭐⭐ ⭐⭐ ⭐⭐
長期維護成本 ⭐⭐ ⭐⭐⭐ ⭐⭐⭐

混合策略:發揮各方案優勢

在實際專案中,通常不會只使用單一方案,而是根據場景混合使用:

// 組合策略範例
const ProjectLayout = () => {
  return (
    <div className="min-h-screen bg-gray-50"> {/* Tailwind 處理 layout */}
      <Header className="header" /> {/* BEM 處理複雜組件 */}
      <StyledMain> {/* CSS-in-JS 處理動態樣式 */}
        <div className="container mx-auto px-4"> {/* Tailwind 處理通用樣式 */}
          <ComplexChart /> {/* 複雜組件使用 CSS-in-JS */}
        </div>
      </StyledMain>
    </div>
  );
};

📊 效能與最佳化考量

各方案的效能特性對比

// Bundle 大小分析 (以中型專案為例)
const performanceComparison = {
  BEM: {
    CSS: '45KB', // 壓縮後
    JS: '0KB',   // 無 JS 運行時
    總計: '45KB'
  },
  'CSS-in-JS': {
    CSS: '12KB', // 動態生成,較小
    JS: '25KB',  // Styled-components 運行時
    總計: '37KB'
  },
  Tailwind: {
    CSS: '8KB',  // 優秀的 purge 機制
    JS: '0KB',   // 無 JS 運行時
    總計: '8KB'
  }
};

// 運行時效能
const runtimePerformance = {
  BEM: 'No runtime overhead',
  'CSS-in-JS': '~0.5ms per component render',
  Tailwind: 'No runtime overhead'
};

最佳化策略實作

// CSS-in-JS 效能最佳化
const OptimizedStyledComponent = styled.div.withConfig({
  shouldForwardProp: (prop) => !['color', 'size'].includes(prop),
})`
  color: ${props => props.color};
  font-size: ${props => props.size};
`;

// Tailwind 最佳化設定
module.exports = {
  content: [
    './src/**/*.{js,jsx,ts,tsx}',
  ],
  safelist: [
    // 動態類別保護
    'bg-red-500',
    'bg-green-500',
    {
      pattern: /bg-(red|green|blue)-(100|200|300)/,
    },
  ],
}

📋 本日重點回顧

  1. CSS 架構演進: 從命名約定到工具化,再到與 JavaScript 深度整合的現代化趨勢
  2. 三大主流方案: BEM 提供穩定可靠的基礎,CSS-in-JS 實現真正的組件化,Tailwind 追求開發效率
  3. 選擇策略: 根據專案規模、團隊能力、維護需求做出合適的技術選擇

🎯 最佳實踐建議

  • 混合使用策略: 根據不同場景選擇最適合的方案,不要拘泥於單一技術
  • 建立設計系統: 無論選擇哪種方案,都要建立一致的設計原則和組件庫
  • 重視效能監控: 定期檢查 CSS bundle 大小和渲染效能
  • 團隊規範統一: 制定明確的編碼規範和最佳實踐文件
  • 避免過度工程: 不要為了技術而技術,選擇符合專案需求的最簡方案
  • 避免頻繁切換: 技術選擇要考慮長期維護成本,避免盲目跟風

🤔 延伸思考

  1. 如何在既有專案中漸進式地導入現代化 CSS 方案?
  2. 設計系統的建立應該優先考慮哪些面向?
  3. 在微前端架構中,如何統一不同子應用的樣式方案?

上一篇
前端框架三強鼎立:React vs Vue vs Angular 的深度解析與選擇策略
下一篇
開發工具鏈整合:打造一套完整的現代化前端工作流
系列文
前端工程師的 Modern Web 實踐之道7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言