React样式处理详解

第7章 样式处理详解

🎨 章节学习目标

本章将全面讲解React应用中的样式处理技术,从基础的CSS应用到高级的样式架构设计。通过学习本章,你将掌握各种样式方案的优缺点、适用场景,以及如何构建可维护、可扩展的样式系统。

📋 学习任务清单

✅ CSS基础与内联样式

✅ CSS模块化与作用域

✅ CSS-in-JS解决方案

✅ 预处理器集成

✅ 组件库和设计系统

✅ 响应式设计

✅ 主题系统

✅ 样式优化和最佳实践

🚀 核心知识点概览

  1. 样式方案对比 - 理解不同样式方案的优缺点和适用场景
  2. 作用域管理 - 掌握样式隔离和命名空间技术
  3. 动态样式 - 实现基于状态和props的动态样式
  4. 主题系统 - 构建灵活可配置的主题架构
  5. 性能优化 - 优化样式的加载、解析和渲染性能
  6. 响应式设计 - 适配不同设备和屏幕尺寸
  7. 工程化配置 - 掌握样式处理工具链的配置和优化
  8. 最佳实践 - 遵循业界成熟的样式开发规范

💡 学习建议

  • 对比学习:通过对比不同方案来理解各自的优缺点
  • 实践驱动:每种方案都要通过实际项目来验证效果
  • 性能优先:在开发过程中始终关注样式的性能影响
  • 渐进增强:从基础方案开始,逐步引入高级特性
  • 团队协作:考虑样式方案在团队开发中的可维护性

7.1 CSS基础与内联样式

7.1.1 React中的样式应用方式

传统CSS类名方式:

// 1. 传统CSS类名 - 最基础的方式
import './Component.css'

function Button() {
  return <button className="btn btn-primary">点击我</button>
}

/* Component.css */
.btn {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}

.btn-primary {
  background-color: #007bff;
  color: white;
}

.btn-primary:hover {
  background-color: #0056b3;
}

内联样式详解:

// 2. 内联样式 - 动态性最强,但有性能限制
function DynamicButton({ disabled, size = 'medium' }) {
  // 样式对象
  const baseStyles = {
    border: 'none',
    borderRadius: '4px',
    cursor: 'pointer',
    transition: 'all 0.2s ease'
  }
  
  // 动态样式
  const dynamicStyles = {
    padding: size === 'small' ? '6px 12px' : 
              size === 'large' ? '12px 24px' : '8px 16px',
    backgroundColor: disabled ? '#6c757d' : '#007bff',
    color: disabled ? '#fff' : '#fff',
    opacity: disabled ? 0.6 : 1,
    cursor: disabled ? 'not-allowed' : 'pointer'
  }
  
  return (
    <button style={{ ...baseStyles, ...dynamicStyles }}>
      {disabled ? '已禁用' : '点击我'}
    </button>
  )
}

// 3. 样式对象的最佳实践
const buttonStyles = {
  // 基础样式
  base: {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    border: 'none',
    borderRadius: '6px',
    fontWeight: '500',
    cursor: 'pointer',
    transition: 'all 0.2s ease',
    outline: 'none'
  },
  
  // 变体样式
  variants: {
    primary: {
      backgroundColor: '#007bff',
      color: 'white'
    },
    secondary: {
      backgroundColor: 'transparent',
      color: '#007bff',
      border: '1px solid #007bff'
    },
    danger: {
      backgroundColor: '#dc3545',
      color: 'white'
    },
    ghost: {
      backgroundColor: 'transparent',
      color: '#212529'
    }
  },
  
  // 尺寸样式
  sizes: {
    small: {
      padding: '6px 12px',
      fontSize: '12px',
      minHeight: '28px'
    },
    medium: {
      padding: '8px 16px',
      fontSize: '14px',
      minHeight: '32px'
    },
    large: {
      padding: '12px 24px',
      fontSize: '16px',
      minHeight: '40px'
    }
  },
  
  // 状态样式
  states: {
    disabled: {
      opacity: 0.6,
      cursor: 'not-allowed',
      pointerEvents: 'none'
    },
    loading: {
      cursor: 'wait',
      pointerEvents: 'none'
    },
    active: {
      transform: 'translateY(1px)',
      boxShadow: 'none'
    }
  }
}

// 使用样式对象
function StyledButton({ 
  variant = 'primary', 
  size = 'medium', 
  disabled = false,
  loading = false,
  children 
}) {
  const styles = {
    ...buttonStyles.base,
    ...buttonStyles.variants[variant],
    ...buttonStyles.sizes[size],
    ...(disabled && buttonStyles.states.disabled),
    ...(loading && buttonStyles.states.loading)
  }
  
  return (
    <button style={styles}>
      {loading ? <Spinner /> : children}
    </button>
  )
}

7.1.2 CSS特殊性与优先级

理解CSS特殊性:

// CSS特殊性计算示例
/*
内联样式 (style="...")           -> 1000
ID选择器 (#header)                -> 0100
类选择器、属性选择器、伪类      -> 0010
元素选择器、伪元素              -> 0001
通配符选择器 (*)                -> 0000
!important                      -> 特殊性最高
*/

// 高优先级样式的处理
function HighPriorityComponent() {
  // 方法1: 使用内联样式覆盖
  const highPriorityStyle = {
    backgroundColor: 'red !important', // 注意:在JS中!important需要配合使用
    // 更好的方式是通过CSS-in-JS库来处理
  }
  
  // 方法2: 使用更具体的选择器(配合CSS Modules)
  return (
    <div 
      className="high-priority-component"
      style={{ color: 'red' }} // 内联样式优先级高
    >
      高优先级文本
    </div>
  )
}

/* CSS中处理优先级 */
.high-priority-component {
  color: blue !important; /* 使用!important强制覆盖 */
}

/* 或者使用更具体的选择器 */
.app .high-priority-component {
  color: green; /* 比单独的类选择器优先级高 */
}

/* ID选择器 */
#high-priority-component {
  color: purple; /* 优先级更高 */
}

动态样式的条件处理:

// 复杂的条件样式处理
function ConditionalStyleComponent({ 
  isActive, 
  isDisabled, 
  hasError, 
  size = 'medium',
  theme = 'light' 
}) {
  // 条件样式对象
  const getConditionalStyles = () => {
    const baseStyles = {
      padding: '12px 20px',
      borderRadius: '8px',
      border: '2px solid',
      fontSize: '14px',
      fontWeight: '500',
      transition: 'all 0.3s ease',
      cursor: isDisabled ? 'not-allowed' : 'pointer'
    }
    
    // 主题相关样式
    const themeStyles = {
      light: {
        backgroundColor: 'white',
        color: '#333333',
        borderColor: '#d1d5db'
      },
      dark: {
        backgroundColor: '#1f2937',
        color: '#f9fafb',
        borderColor: '#4b5563'
      }
    }
    
    // 状态相关样式
    const stateStyles = {
      active: {
        backgroundColor: theme === 'light' ? '#3b82f6' : '#2563eb',
        color: 'white',
        borderColor: 'transparent',
        transform: 'scale(1.02)',
        boxShadow: '0 4px 12px rgba(59, 130, 246, 0.15)'
      },
      disabled: {
        opacity: 0.5,
        backgroundColor: theme === 'light' ? '#f3f4f6' : '#374151',
        cursor: 'not-allowed'
      },
      error: {
        backgroundColor: '#fef2f2',
        color: '#dc2626',
        borderColor: '#dc2626'
      }
    }
    
    // 尺寸相关样式
    const sizeStyles = {
      small: {
        padding: '8px 12px',
        fontSize: '12px'
      },
      medium: {
        padding: '12px 20px',
        fontSize: '14px'
      },
      large: {
        padding: '16px 24px',
        fontSize: '16px'
      }
    }
    
    // 组合所有样式
    const finalStyles = {
      ...baseStyles,
      ...themeStyles[theme],
      ...sizeStyles[size]
    }
    
    // 应用状态样式(按优先级)
    if (isDisabled) {
      Object.assign(finalStyles, stateStyles.disabled)
    } else if (hasError) {
      Object.assign(finalStyles, stateStyles.error)
    } else if (isActive) {
      Object.assign(finalStyles, stateStyles.active)
    }
    
    return finalStyles
  }
  
  return (
    <button style={getConditionalStyles()}>
      动态样式示例
    </button>
  )
}

// 更简洁的条件样式处理方式
const useConditionalStyles = (conditions, styleMap) => {
  return useMemo(() => {
    const activeStyles = Object.keys(conditions)
      .filter(key => conditions[key])
      .reduce((styles, key) => {
        return { ...styles, ...styleMap[key] }
      }, {})
    
    return activeStyles
  }, [conditions, styleMap])
}

// 使用自定义Hook
function SmartButton({ 
  variant = 'default', 
  size = 'medium', 
  disabled = false 
}) {
  const conditions = {
    [variant]: true,
    [size]: true,
    disabled
  }
  
  const styleMap = {
    default: { backgroundColor: '#e5e7eb', color: '#111827' },
    primary: { backgroundColor: '#3b82f6', color: 'white' },
    secondary: { backgroundColor: '#6b7280', color: 'white' },
    small: { padding: '6px 12px', fontSize: '12px' },
    medium: { padding: '8px 16px', fontSize: '14px' },
    large: { padding: '12px 24px', fontSize: '16px' },
    disabled: { opacity: 0.5, cursor: 'not-allowed' }
  }
  
  const styles = useConditionalStyles(conditions, styleMap)
  
  return <button style={styles}>智能按钮</button>
}

7.1.3 样式性能优化

样式计算优化:

// 1. 避免在render中创建样式对象
function BadPerformanceButton({ color, size }) {
  // ❌ 每次渲染都会创建新的样式对象
  const styles = {
    backgroundColor: color,
    padding: size === 'large' ? '16px' : '8px'
  }
  
  return <button style={styles}>性能差的按钮</button>
}

// 2. 使用useMemo缓存样式对象
function GoodPerformanceButton({ color, size }) {
  // ✅ 使用useMemo缓存样式对象
  const styles = useMemo(() => ({
    backgroundColor: color,
    padding: size === 'large' ? '16px' : '8px',
    border: 'none',
    borderRadius: '4px',
    cursor: 'pointer'
  }), [color, size])
  
  return <button style={styles}>性能好的按钮</button>
}

// 3. 使用useCallback优化事件处理器
function OptimizedButton({ onClick, children, ...props }) {
  // 缓存样式对象
  const styles = useMemo(() => ({
    padding: '12px 24px',
    backgroundColor: '#007bff',
    color: 'white',
    border: 'none',
    borderRadius: '4px',
    cursor: 'pointer',
    transition: 'all 0.2s ease'
  }), [])
  
  // 缓存点击处理器
  const handleClick = useCallback((event) => {
    event.stopPropagation()
    onClick?.(event)
  }, [onClick])
  
  return (
    <button 
      style={styles} 
      onClick={handleClick}
      {...props}
    >
      {children}
    </button>
  )
}

批量样式更新:

// 批量样式更新工具
class StyleManager {
  constructor() {
    this.styleCache = new Map()
    this.batchedUpdates = new Set()
    this.updateTimer = null
  }
  
  // 获取缓存的样式对象
  getStyle(config) {
    const key = this.generateKey(config)
    
    if (this.styleCache.has(key)) {
      return this.styleCache.get(key)
    }
    
    const style = this.createStyle(config)
    this.styleCache.set(key, style)
    return style
  }
  
  // 生成缓存键
  generateKey(config) {
    return JSON.stringify(config)
  }
  
  // 创建样式对象
  createStyle({ variant = 'default', size = 'medium', disabled = false }) {
    return {
      padding: this.getPadding(size),
      fontSize: this.getFontSize(size),
      backgroundColor: this.getBackgroundColor(variant, disabled),
      color: this.getTextColor(variant, disabled),
      cursor: disabled ? 'not-allowed' : 'pointer',
      opacity: disabled ? 0.6 : 1,
      transition: 'all 0.2s ease'
    }
  }
  
  getPadding(size) {
    const paddingMap = {
      small: '6px 12px',
      medium: '8px 16px',
      large: '12px 24px'
    }
    return paddingMap[size] || paddingMap.medium
  }
  
  getFontSize(size) {
    const fontSizeMap = {
      small: '12px',
      medium: '14px',
      large: '16px'
    }
    return fontSizeMap[size] || fontSizeMap.medium
  }
  
  getBackgroundColor(variant, disabled) {
    if (disabled) return '#6c757d'
    
    const colorMap = {
      default: '#e9ecef',
      primary: '#007bff',
      secondary: '#6c757d',
      success: '#28a745',
      danger: '#dc3545'
    }
    return colorMap[variant] || colorMap.default
  }
  
  getTextColor(variant, disabled) {
    if (disabled) return '#ffffff'
    
    const colorMap = {
      default: '#212529',
      primary: '#ffffff',
      secondary: '#ffffff',
      success: '#ffffff',
      danger: '#ffffff'
    }
    return colorMap[variant] || colorMap.default
  }
  
  // 批量更新样式
  batchUpdate(updateFn) {
    this.batchedUpdates.add(updateFn)
    
    if (this.updateTimer) {
      clearTimeout(this.updateTimer)
    }
    
    this.updateTimer = setTimeout(() => {
      this.flushUpdates()
    }, 16) // 一帧的时间
  }
  
  flushUpdates() {
    this.batchedUpdates.forEach(updateFn => {
      updateFn()
    })
    this.batchedUpdates.clear()
    this.updateTimer = null
  }
}

const styleManager = new StyleManager()

// 使用样式管理器
function ManagedButton({ variant, size, disabled, children }) {
  const [currentState, setCurrentState] = useState({
    variant: variant || 'default',
    size: size || 'medium',
    disabled: disabled || false
  })
  
  // 获取缓存的样式
  const styles = styleManager.getStyle(currentState)
  
  // 批量更新状态
  const updateState = useCallback((updates) => {
    styleManager.batchUpdate(() => {
      setCurrentState(prev => ({ ...prev, ...updates }))
    })
  }, [])
  
  return (
    <button 
      style={styles}
      onMouseDown={() => updateState({ active: true })}
      onMouseUp={() => updateState({ active: false })}
      {...(disabled ? { disabled: true } : {})}
    >
      {children}
    </button>
  )
}

7.1.4 CSS变量与动态主题

CSS变量的React应用:

// 使用CSS变量实现动态主题
function ThemeProvider({ children, theme = 'light' }) {
  useEffect(() => {
    // 应用主题变量
    const root = document.documentElement
    
    if (theme === 'dark') {
      root.style.setProperty('--primary-color', '#3b82f6')
      root.style.setProperty('--secondary-color', '#6b7280')
      root.style.setProperty('--background-color', '#1f2937')
      root.style.setProperty('--text-color', '#f9fafb')
      root.style.setProperty('--border-color', '#4b5563')
    } else {
      root.style.setProperty('--primary-color', '#007bff')
      root.style.setProperty('--secondary-color', '#6c757d')
      root.style.setProperty('--background-color', '#ffffff')
      root.style.setProperty('--text-color', '#212529')
      root.style.setProperty('--border-color', '#dee2e6')
    }
  }, [theme])
  
  return <>{children}</>
}

// 使用CSS变量的组件
function ThemedComponent() {
  const styles = {
    container: {
      backgroundColor: 'var(--background-color)',
      color: 'var(--text-color)',
      border: `1px solid var(--border-color)`,
      borderRadius: '8px',
      padding: '20px',
      margin: '10px 0'
    },
    button: {
      backgroundColor: 'var(--primary-color)',
      color: 'white',
      border: 'none',
      borderRadius: '4px',
      padding: '8px 16px',
      cursor: 'pointer'
    }
  }
  
  return (
    <div style={styles.container}>
      <h3>主题化组件</h3>
      <button style={styles.button}>主题按钮</button>
    </div>
  )
}

// 动态CSS变量管理
function useCSSVariables() {
  const setVariable = useCallback((name, value) => {
    document.documentElement.style.setProperty(name, value)
  }, [])
  
  const getVariable = useCallback((name) => {
    return getComputedStyle(document.documentElement)
      .getPropertyValue(name).trim()
  }, [])
  
  const removeVariable = useCallback((name) => {
    document.documentElement.style.removeProperty(name)
  }, [])
  
  const setTheme = useCallback((theme) => {
    Object.entries(theme).forEach(([key, value]) => {
      setVariable(`--${key}`, value)
    })
  }, [setVariable])
  
  return {
    setVariable,
    getVariable,
    removeVariable,
    setTheme
  }
}

// 主题管理组件
function ThemeManager() {
  const { setTheme, getVariable } = useCSSVariables()
  const [currentTheme, setCurrentTheme] = useState('light')
  
  const themes = {
    light: {
      'primary-color': '#007bff',
      'secondary-color': '#6c757d',
      'background-color': '#ffffff',
      'text-color': '#212529',
      'border-color': '#dee2e6'
    },
    dark: {
      'primary-color': '#3b82f6',
      'secondary-color': '#6b7280',
      'background-color': '#1f2937',
      'text-color': '#f9fafb',
      'border-color': '#4b5563'
    },
    purple: {
      'primary-color': '#8b5cf6',
      'secondary-color': '#a78bfa',
      'background-color': '#faf5ff',
      'text-color': '#1f2937',
      'border-color': '#d8b4fe'
    }
  }
  
  const handleThemeChange = (themeName) => {
    setCurrentTheme(themeName)
    setTheme(themes[themeName])
  }
  
  return (
    <div>
      <div>当前主题: {currentTheme}</div>
      <div>主色调: {getVariable('--primary-color')}</div>
      
      {Object.keys(themes).map(themeName => (
        <button
          key={themeName}
          onClick={() => handleThemeChange(themeName)}
          style={{ margin: '5px' }}
        >
          {themeName}
        </button>
      ))}
    </div>
  )
}

通过这一节的学习,你已经掌握了React中基础样式处理的核心概念,包括内联样式、CSS特殊性、性能优化和CSS变量的使用。这些是构建复杂样式系统的基础,为后续学习更高级的样式方案打下了坚实的基础。


7.2 CSS模块化与作用域

7.2.1 CSS Modules基础

CSS Modules简介:

// CSS Modules 提供局部作用域的CSS
// 构建时会自动生成唯一的类名

// Button.module.css
.button {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  background-color: #007bff;
  color: white;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.2s ease;
}

.button:hover {
  background-color: #0056b3;
  transform: translateY(-1px);
  box-shadow: 0 2px 8px rgba(0, 123, 255, 0.3);
}

.button:active {
  transform: translateY(0);
  box-shadow: none;
}

.button--disabled {
  background-color: #6c757d;
  cursor: not-allowed;
  opacity: 0.6;
}

.button--large {
  padding: 12px 24px;
  font-size: 16px;
}

.button--small {
  padding: 6px 12px;
  font-size: 12px;
}

// Button.jsx
import React from 'react'
import styles from './Button.module.css'

function Button({ 
  children, 
  disabled = false, 
  size = 'medium', 
  onClick 
}) {
  const buttonClasses = [
    styles.button,
    disabled ? styles['button--disabled'] : '',
    size === 'large' ? styles['button--large'] : '',
    size === 'small' ? styles['button--small'] : ''
  ].filter(Boolean).join(' ')
  
  return (
    <button 
      className={buttonClasses}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
    </button>
  )
}

// 编译后的HTML示例
// <button class="Button_button__1x2j3 Button_button--large__4y5z6">
//   点击我
// </button>

CSS Modules配置:

// webpack.config.js 或 create-react-app 的配置
module.exports = {
  module: {
    rules: [
      {
        test: /\.module\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: {
                // 自定义类名格式
                localIdentName: '[name]__[local]--[hash:base64:5]',
                // 导出全局类名
                exportGlobals: false,
                // 导出本地类名
                exportLocalsConvention: 'camelCase'
              }
            }
          }
        ]
      },
      {
        test: /\.css$/,
        exclude: /\.module\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}

// Vite配置示例
// vite.config.js
export default {
  css: {
    modules: {
      localsConvention: 'camelCase',
      generateScopedName: '[name]__[local]___[hash:base64:5]'
    }
  }
}

TypeScript集成:

// types/modules.d.ts
declare module '*.module.css' {
  const classes: { [key: string]: string }
  export default classes
}

declare module '*.module.scss' {
  const classes: { [key: string]: string }
  export default classes
}

// 强类型CSS Modules
// Button.module.css
.base {
  composes: base from './common.module.css';
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
}

.primary {
  composes: base;
  background-color: #007bff;
  color: white;
}

// Button.tsx
import styles from './Button.module.css'
import { ButtonVariant, ButtonSize } from './types'

interface ButtonProps {
  variant?: ButtonVariant
  size?: ButtonSize
  disabled?: boolean
  children: React.ReactNode
  onClick?: () => void
}

type ButtonClasses = {
  button: string
  disabled: string
  large: string
  small: string
}

function Button({ 
  variant = 'primary', 
  size = 'medium', 
  disabled = false,
  children,
  onClick 
}: ButtonProps) {
  const buttonClasses: ButtonClasses = styles
  
  const className = [
    buttonClasses.button,
    buttonClasses[variant],
    disabled ? buttonClasses.disabled : '',
    size !== 'medium' ? buttonClasses[size] : ''
  ].filter(Boolean).join(' ')
  
  return (
    <button className={className} onClick={onClick} disabled={disabled}>
      {children}
    </button>
  )
}

7.2.2 Composes与继承

CSS Composes详解:

/* base.module.css - 基础样式 */
.base {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 6px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  outline: none;
  user-select: none;
}

/* 按钮基础样式 */
.buttonBase {
  composes: base;
  padding: 8px 16px;
  font-size: 14px;
  line-height: 1.5;
}

/* 输入框基础样式 */
.inputBase {
  composes: base;
  padding: 10px 12px;
  font-size: 14px;
  background-color: white;
  border: 1px solid #d1d5db;
}

/* sizes.module.css - 尺寸样式 */
.small {
  padding: 6px 12px;
  font-size: 12px;
  min-height: 28px;
}

.medium {
  padding: 8px 16px;
  font-size: 14px;
  min-height: 36px;
}

.large {
  padding: 12px 24px;
  font-size: 16px;
  min-height: 44px;
}

/* variants.module.css - 变体样式 */
.primary {
  background-color: #007bff;
  color: white;
}

.primary:hover {
  background-color: #0056b3;
}

.secondary {
  background-color: transparent;
  color: #007bff;
  border: 1px solid #007bff;
}

.secondary:hover {
  background-color: #f8f9fa;
}

.danger {
  background-color: #dc3545;
  color: white;
}

.danger:hover {
  background-color: #c82333;
}
// 使用composes的组件
import React from 'react'
import baseStyles from './base.module.css'
import sizeStyles from './sizes.module.css'
import variantStyles from './variants.module.css'

function ComposableButton({ 
  size = 'medium',
  variant = 'primary',
  children,
  ...props 
}) {
  const className = [
    baseStyles.buttonBase,
    sizeStyles[size],
    variantStyles[variant]
  ].join(' ')
  
  return (
    <button className={className} {...props}>
      {children}
    </button>
  )
}

// 高级composes用法
/* advanced.module.css */
.card {
  padding: 20px;
  border-radius: 8px;
  background-color: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.elevated {
  composes: card;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  transform: translateY(-2px);
}

.bordered {
  composes: card;
  border: 1px solid #e5e7eb;
  box-shadow: none;
}

.transparent {
  composes: card;
  background-color: transparent;
  box-shadow: none;
}

/* 条件样式 */
.highlight {
  composes: card;
  border-left: 4px solid #3b82f6;
  background-color: #eff6ff;
}

.warning {
  composes: highlight;
  border-left-color: #f59e0b;
  background-color: #fffbeb;
}

.error {
  composes: highlight;
  border-left-color: #ef4444;
  background-color: #fef2f2;
}

7.2.3 全局样式与局部样式

处理全局样式:

/* global.css - 全局样式文件 */
/* 这些样式不会被模块化,会全局生效 */

:root {
  /* CSS变量 */
  --primary-color: #007bff;
  --secondary-color: #6c757d;
  --success-color: #28a745;
  --danger-color: #dc3545;
  --warning-color: #ffc107;
  --info-color: #17a2b8;
  
  --font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-family-monospace: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
  
  --font-size-base: 14px;
  --line-height-base: 1.5;
  
  --border-radius: 4px;
  --box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

/* 全局重置样式 */
* {
  box-sizing: border-box;
}

html {
  font-size: 16px;
  line-height: 1.15;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-family-base);
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
  color: #212529;
  background-color: #ffffff;
}

/* 全局工具类 */
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }

.d-flex { display: flex; }
.d-none { display: none; }
.d-block { display: block; }

.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.align-center { align-items: center; }

/* 响应式工具类 */
@media (max-width: 768px) {
  .d-md-none { display: none; }
  .d-md-block { display: block; }
  .text-md-center { text-align: center; }
}
// 在CSS Modules中使用全局样式
// Component.module.css
.container {
  /* 局部样式 */
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;
}

/* 引用全局类 */
.header :global(.navbar) {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
}

/* 混合使用全局和局部样式 */
.card {
  composes: card from './base.module.css';
  /* :global() 内的选择器是全局的 */
}

.card :global(.btn) {
  margin-top: 10px;
}

/* 多个:global()选择器 */
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
}

.modal :global(.modal-dialog) {
  position: relative;
  max-width: 500px;
  margin: 50px auto;
  background-color: white;
  border-radius: var(--border-radius);
}

.modal :global(.modal-header),
.modal :global(.modal-body),
.modal :global(.modal-footer) {
  padding: 15px;
}

7.2.4 CSS Modules的高级用法

动态类名处理:

// 类名管理工具
import styles from './Component.module.css'

// 基础类名组合函数
function classNames(...classes) {
  return classes.filter(Boolean).join(' ')
}

// 条件类名处理
function conditionalClassNames(classMap) {
  return Object.entries(classMap)
    .filter(([className, condition]) => Boolean(condition))
    .map(([className]) => className)
    .join(' ')
}

// 使用示例
function AdvancedButton({ 
  variant = 'primary',
  size = 'medium',
  disabled = false,
  loading = false,
  fullWidth = false,
  children 
}) {
  const baseClasses = [
    styles.button,
    styles[variant],
    styles[size]
  ]
  
  const conditionalClasses = {
    [styles.disabled]: disabled,
    [styles.loading]: loading,
    [styles.fullWidth]: fullWidth
  }
  
  const className = classNames(
    ...baseClasses,
    conditionalClassNames(conditionalClasses)
  )
  
  return (
    <button className={className} disabled={disabled}>
      {loading ? <Spinner /> : children}
    </button>
  )
}

// 更高级的类名管理Hook
function useCSSModules(styles, baseClass = '') {
  const getClassName = useCallback((classSpec) => {
    if (typeof classSpec === 'string') {
      return styles[classSpec] || classSpec
    }
    
    if (Array.isArray(classSpec)) {
      return classSpec
        .map(cls => styles[cls] || cls)
        .filter(Boolean)
        .join(' ')
    }
    
    if (typeof classSpec === 'object') {
      return Object.entries(classSpec)
        .filter(([className, condition]) => Boolean(condition))
        .map(([className]) => styles[className] || className)
        .join(' ')
    }
    
    return ''
  }, [styles])
  
  const combineClasses = useCallback((...classSpecs) => {
    return classNames(
      baseClass,
      ...classSpecs.map(getClassName)
    )
  }, [baseClass, getClassName])
  
  return { getClassName, combineClasses }
}

// 使用Hook的组件
function HookButton(props) {
  const { combineClasses } = useCSSModules(styles, styles.button)
  
  const className = combineClasses(
    props.variant,
    props.size,
    {
      disabled: props.disabled,
      loading: props.loading
    }
  )
  
  return <button className={className}>{props.children}</button>
}

CSS Modules与主题系统:

// 主题化的CSS Modules
// themes/theme-light.module.css
:root {
  --bg-primary: #ffffff;
  --bg-secondary: #f8f9fa;
  --text-primary: #212529;
  --text-secondary: #6c757d;
  --border-color: #dee2e6;
  --primary-color: #007bff;
  --success-color: #28a745;
  --danger-color: #dc3545;
}

.container {
  background-color: var(--bg-primary);
  color: var(--text-primary);
}

.button {
  background-color: var(--primary-color);
  color: white;
}

// themes/theme-dark.module.css
:root {
  --bg-primary: #1a1a1a;
  --bg-secondary: #2d2d2d;
  --text-primary: #ffffff;
  --text-secondary: #a0a0a0;
  --border-color: #404040;
  --primary-color: #4dabf7;
  --success-color: #51cf66;
  --danger-color: #ff6b6b;
}

// 主题管理器
function ThemeProvider({ children, theme = 'light' }) {
  const themeStyles = useMemo(() => {
    if (theme === 'dark') {
      return import('./themes/theme-dark.module.css')
    }
    return import('./themes/theme-light.module.css')
  }, [theme])
  
  // 动态加载主题样式
  useEffect(() => {
    themeStyles.then(module => {
      // 应用主题样式
      Object.entries(module).forEach(([key, value]) => {
        if (key.startsWith('theme-')) {
          document.documentElement.style.setProperty(key, value)
        }
      })
    })
  }, [themeStyles])
  
  return <>{children}</>
}

// 主题感知组件
function ThemedCard({ theme, children }) {
  const cardStyles = useMemo(() => {
    return import(`./themes/theme-${theme}.module.css`)
  }, [theme])
  
  const [styles, setStyles] = useState(null)
  
  useEffect(() => {
    cardStyles.then(setStyles)
  }, [cardStyles])
  
  if (!styles) return <div>Loading...</div>
  
  return (
    <div className={styles.card}>
      {children}
    </div>
  )
}

CSS Modules的性能优化:

// 样式预加载和缓存
class CSSModulesCache {
  constructor() {
    this.cache = new Map()
    this.loadingPromises = new Map()
  }
  
  // 加载样式模块
  async loadStyles(modulePath) {
    if (this.cache.has(modulePath)) {
      return this.cache.get(modulePath)
    }
    
    if (this.loadingPromises.has(modulePath)) {
      return this.loadingPromises.get(modulePath)
    }
    
    const loadPromise = this.doLoadStyles(modulePath)
    this.loadingPromises.set(modulePath, loadPromise)
    
    try {
      const styles = await loadPromise
      this.cache.set(modulePath, styles)
      return styles
    } finally {
      this.loadingPromises.delete(modulePath)
    }
  }
  
  async doLoadStyles(modulePath) {
    // 动态导入CSS模块
    const module = await import(modulePath)
    return module.default || module
  }
  
  // 预加载样式
  preload(modulePath) {
    if (!this.cache.has(modulePath)) {
      this.loadStyles(modulePath)
    }
  }
  
  // 清除缓存
  clear(modulePath) {
    if (modulePath) {
      this.cache.delete(modulePath)
      this.loadingPromises.delete(modulePath)
    } else {
      this.cache.clear()
      this.loadingPromises.clear()
    }
  }
  
  // 获取缓存统计
  getStats() {
    return {
      cached: this.cache.size,
      loading: this.loadingPromises.size,
      keys: Array.from(this.cache.keys())
    }
  }
}

const stylesCache = new CSSModulesCache()

// 使用缓存的样式组件
function CachedStyledComponent({ theme = 'light', children }) {
  const [styles, setStyles] = useState(null)
  
  useEffect(() => {
    stylesCache.loadStyles(`./themes/theme-${theme}.module.css`)
      .then(setStyles)
      .catch(console.error)
  }, [theme])
  
  // 预加载其他主题
  useEffect(() => {
    ['dark', 'light'].forEach(t => {
      if (t !== theme) {
        stylesCache.preload(`./themes/theme-${t}.module.css`)
      }
    })
  }, [theme])
  
  if (!styles) {
    return <div className="loading">Loading styles...</div>
  }
  
  return (
    <div className={styles.container}>
      {children}
    </div>
  )
}

// 样式预加载策略
function useStylePreloading() {
  const preloadCriticalStyles = useCallback(() => {
    const criticalModules = [
      './components/Button.module.css',
      './components/Navigation.module.css',
      './components/Header.module.css'
    ]
    
    criticalModules.forEach(modulePath => {
      stylesCache.preload(modulePath)
    })
  }, [])
  
  const preloadUserInteractionStyles = useCallback(() => {
    const interactionModules = [
      './components/Modal.module.css',
      './components/Form.module.css',
      './components/Table.module.css'
    ]
    
    interactionModules.forEach(modulePath => {
      stylesCache.preload(modulePath)
    })
  }, [])
  
  return {
    preloadCriticalStyles,
    preloadUserInteractionStyles
  }
}

CSS Modules提供了强大的作用域隔离和组合能力,通过合理的使用可以构建出高度可维护的样式系统。结合TypeScript和现代构建工具,可以进一步提升开发体验和代码质量。


7.3 CSS-in-JS解决方案

7.3.1 CSS-in-JS概述与优势

CSS-in-JS的核心理念:

// CSS-in-JS的基本概念
// 1. 使用JavaScript编写CSS
// 2. 样式与组件紧密耦合
// 3. 运行时或编译时处理样式
// 4. 自动作用域隔离

// 传统CSS vs CSS-in-JS对比

// 传统CSS方式
/*
styles.css
.button {
  padding: 8px 16px;
  background-color: blue;
  color: white;
}

Button.js
<button className="button">Click me</button>
*/

// CSS-in-JS方式
const StyledButton = styled.button`
  padding: 8px 16px;
  background-color: ${props => props.primary ? 'blue' : 'gray'};
  color: white;
  border-radius: 4px;
  
  &:hover {
    opacity: 0.8;
  }
`

<StyledButton primary>Click me</StyledButton>

// CSS-in-JS的主要优势
const cssInJSAdvantages = {
  // 1. 样式隔离
  styleIsolation: '自动生成唯一类名,避免样式冲突',
  
  // 2. 动态样式
  dynamicStyling: '基于props和状态动态生成样式',
  
  // 3. 组件化
  componentBased: '样式与组件逻辑紧密结合',
  
  // 4. 主题系统
  theming: '轻松实现主题切换和品牌定制',
  
  // 5. 死代码消除
  deadCodeElimination: '只包含使用到的样式',
  
  // 6. 类型安全
  typeSafety: 'TypeScript支持,编译时检查',
  
  // 7. 工具集成
  tooling: '与JavaScript工具链完美集成'
}

7.3.2 styled-components详解

styled-components基础用法:

# 安装styled-components
npm install styled-components
npm install @types/styled-components  # TypeScript支持
// 基础styled-components用法
import styled, { css, ThemeProvider, createGlobalStyle } from 'styled-components'

// 1. 基础组件样式化
const Button = styled.button`
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  font-size: 14px;
  cursor: pointer;
  transition: all 0.2s ease;
  
  /* 基于props的动态样式 */
  background-color: ${props => {
    if (props.disabled) return '#6c757d'
    if (props.danger) return '#dc3545'
    if (props.primary) return '#007bff'
    return '#6c757d'
  }};
  
  color: ${props => props.disabled ? '#fff' : '#fff'};
  opacity: ${props => props.disabled ? 0.6 : 1};
  
  /* 条件样式 */
  ${props => props.fullWidth && css`
    width: 100%;
    display: block;
  `}
  
  /* 嵌套选择器 */
  &:hover {
    opacity: ${props => props.disabled ? 0.6 : 0.8};
    transform: translateY(-1px);
  }
  
  &:active {
    transform: translateY(0);
  }
  
  /* 伪元素 */
  &::after {
    content: ${props => props.loading ? '""' : 'none'};
    display: inline-block;
    width: 12px;
    height: 12px;
    margin-left: 8px;
    border: 2px solid transparent;
    border-top: 2px solid currentColor;
    border-radius: 50%;
    animation: spin 1s linear infinite;
  }
  
  @keyframes spin {
    to {
      transform: rotate(360deg);
    }
  }
`

// 2. 继承和扩展
const PrimaryButton = styled(Button)`
  background-color: #007bff;
  border: 2px solid #007bff;
  
  &:hover {
    background-color: transparent;
    color: #007bff;
  }
`

const OutlineButton = styled(Button)`
  background-color: transparent;
  border: 2px solid currentColor;
  
  &:hover {
    background-color: currentColor;
    color: white;
  }
`

// 3. 使用attrs设置默认属性
const Input = styled.input.attrs({
  type: 'text',
  placeholder: '请输入内容...',
  autoComplete: 'off'
})`
  padding: 10px 12px;
  border: 1px solid #d1d5db;
  border-radius: 4px;
  font-size: 14px;
  outline: none;
  transition: border-color 0.2s ease;
  
  &:focus {
    border-color: #3b82f6;
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
  }
  
  &:disabled {
    background-color: #f3f4f6;
    cursor: not-allowed;
  }
`

// 4. 样式化的组件
const Card = styled.div`
  padding: 20px;
  border-radius: 8px;
  background-color: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  
  ${props => props.elevated && css`
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
    transform: translateY(-2px);
  `}
  
  ${props => props.bordered && css`
    border: 1px solid #e5e7eb;
    box-shadow: none;
  `}
  
  ${props => props.transparent && css`
    background-color: transparent;
    box-shadow: none;
  `}
`

// 5. 动态样式函数
const getVariantStyles = (variant) => {
  const variants = {
    primary: css`
      background-color: #007bff;
      color: white;
      border-color: #007bff;
      
      &:hover {
        background-color: #0056b3;
      }
    `,
    secondary: css`
      background-color: #6c757d;
      color: white;
      border-color: #6c757d;
      
      &:hover {
        background-color: #545b62;
      }
    `,
    success: css`
      background-color: #28a745;
      color: white;
      border-color: #28a745;
      
      &:hover {
        background-color: #218838;
      }
    `,
    danger: css`
      background-color: #dc3545;
      color: white;
      border-color: #dc3545;
      
      &:hover {
        background-color: #c82333;
      }
    `
  }
  
  return variants[variant] || variants.primary
}

const DynamicButton = styled.button`
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  font-size: 14px;
  cursor: pointer;
  transition: all 0.2s ease;
  
  ${props => getVariantStyles(props.variant)}
  
  ${props => props.fullWidth && css`
    width: 100%;
  `}
`

主题系统集成:

// 主题定义
const lightTheme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    success: '#28a745',
    danger: '#dc3545',
    warning: '#ffc107',
    info: '#17a2b8',
    light: '#f8f9fa',
    dark: '#343a40',
    
    background: '#ffffff',
    surface: '#f8f9fa',
    text: '#212529',
    textSecondary: '#6c757d',
    border: '#dee2e6'
  },
  
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px',
    xxl: '48px'
  },
  
  typography: {
    fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
    fontSize: {
      xs: '12px',
      sm: '14px',
      md: '16px',
      lg: '18px',
      xl: '20px',
      xxl: '24px'
    },
    fontWeight: {
      light: 300,
      normal: 400,
      medium: 500,
      semibold: 600,
      bold: 700
    }
  },
  
  borderRadius: {
    sm: '2px',
    md: '4px',
    lg: '8px',
    xl: '12px'
  },
  
  shadows: {
    sm: '0 1px 2px rgba(0, 0, 0, 0.05)',
    md: '0 2px 8px rgba(0, 0, 0, 0.1)',
    lg: '0 8px 24px rgba(0, 0, 0, 0.15)',
    xl: '0 12px 40px rgba(0, 0, 0, 0.2)'
  }
}

const darkTheme = {
  ...lightTheme,
  colors: {
    ...lightTheme.colors,
    background: '#1a1a1a',
    surface: '#2d2d2d',
    text: '#ffffff',
    textSecondary: '#a0a0a0',
    border: '#404040',
    primary: '#4dabf7',
    secondary: '#868e96',
    success: '#51cf66',
    danger: '#ff6b6b',
    warning: '#ffd43b',
    info: '#74c0fc',
    light: '#495057',
    dark: '#f8f9fa'
  }
}

// 主题化组件
const ThemedButton = styled.button`
  padding: ${props => props.theme.spacing.md};
  border: none;
  border-radius: ${props => props.theme.borderRadius.md};
  font-family: ${props => props.theme.typography.fontFamily};
  font-size: ${props => props.theme.typography.fontSize.md};
  font-weight: ${props => props.theme.typography.fontWeight.medium};
  cursor: pointer;
  transition: all 0.2s ease;
  background-color: ${props => props.theme.colors.primary};
  color: white;
  
  &:hover {
    opacity: 0.8;
    transform: translateY(-1px);
  }
  
  &:active {
    transform: translateY(0);
  }
`

const ThemedCard = styled.div`
  padding: ${props => props.theme.spacing.lg};
  border-radius: ${props => props.theme.borderRadius.lg};
  background-color: ${props => props.theme.colors.surface};
  color: ${props => props.theme.colors.text};
  box-shadow: ${props => props.theme.shadows.md};
  border: 1px solid ${props => props.theme.colors.border};
  
  & > h1, & > h2, & > h3 {
    margin-top: 0;
    color: ${props => props.theme.colors.text};
  }
  
  & > p:last-child {
    margin-bottom: 0;
  }
`

// 主题提供者和消费者
function App() {
  const [theme, setTheme] = useState('light')
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light')
  }
  
  return (
    <ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
      <div>
        <ThemedCard>
          <h1>主题化应用</h1>
          <p>这是一个支持主题切换的应用示例</p>
          <ThemedButton onClick={toggleTheme}>
            切换到{theme === 'light' ? '深色' : '浅色'}主题
          </ThemedButton>
        </ThemedCard>
      </div>
    </ThemeProvider>
  )
}

全局样式和重置:

// 全局样式组件
const GlobalStyle = createGlobalStyle`
  * {
    box-sizing: border-box;
  }
  
  html {
    font-size: 16px;
    line-height: 1.15;
    -webkit-text-size-adjust: 100%;
  }
  
  body {
    margin: 0;
    font-family: ${props => props.theme.typography.fontFamily};
    font-size: ${props => props.theme.typography.fontSize.md};
    line-height: 1.5;
    color: ${props => props.theme.colors.text};
    background-color: ${props => props.theme.colors.background};
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }
  
  /* 重置常用元素 */
  h1, h2, h3, h4, h5, h6 {
    margin: 0 0 ${props => props.theme.spacing.md};
    font-weight: ${props => props.theme.typography.fontWeight.semibold};
    color: ${props => props.theme.colors.text};
  }
  
  p {
    margin: 0 0 ${props => props.theme.spacing.md};
    line-height: 1.6;
  }
  
  a {
    color: ${props => props.theme.colors.primary};
    text-decoration: none;
    
    &:hover {
      text-decoration: underline;
    }
  }
  
  /* 滚动条样式 */
  ::-webkit-scrollbar {
    width: 8px;
    height: 8px;
  }
  
  ::-webkit-scrollbar-track {
    background: ${props => props.theme.colors.surface};
  }
  
  ::-webkit-scrollbar-thumb {
    background: ${props => props.theme.colors.border};
    border-radius: 4px;
    
    &:hover {
      background: ${props => props.theme.colors.textSecondary};
    }
  }
  
  /* 选择文本样式 */
  ::selection {
    background-color: ${props => props.theme.colors.primary};
    color: white;
  }
  
  /* 焦点样式 */
  :focus {
    outline: 2px solid ${props => props.theme.colors.primary};
    outline-offset: 2px;
  }
  
  /* 禁用状态 */
  :disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }
`

// 工具类
const UtilityStyles = createGlobalStyle`
  /* 文本对齐 */
  .text-center { text-align: center; }
  .text-left { text-align: left; }
  .text-right { text-align: right; }
  
  /* 显示控制 */
  .d-none { display: none; }
  .d-block { display: block; }
  .d-inline { display: inline; }
  .d-flex { display: flex; }
  .d-inline-flex { display: inline-flex; }
  .d-grid { display: grid; }
  
  /* Flex工具类 */
  .flex-row { flex-direction: row; }
  .flex-column { flex-direction: column; }
  .flex-wrap { flex-wrap: wrap; }
  .flex-nowrap { flex-wrap: nowrap; }
  
  .justify-start { justify-content: flex-start; }
  .justify-center { justify-content: center; }
  .justify-end { justify-content: flex-end; }
  .justify-between { justify-content: space-between; }
  .justify-around { justify-content: space-around; }
  
  .align-start { align-items: flex-start; }
  .align-center { align-items: center; }
  .align-end { align-items: flex-end; }
  .align-stretch { align-items: stretch; }
  
  /* 间距工具类 */
  .m-0 { margin: 0; }
  .m-1 { margin: ${props => props.theme.spacing.xs}; }
  .m-2 { margin: ${props => props.theme.spacing.sm}; }
  .m-3 { margin: ${props => props.theme.spacing.md}; }
  .m-4 { margin: ${props => props.theme.spacing.lg}; }
  .m-5 { margin: ${props => props.theme.spacing.xl}; }
  
  .p-0 { padding: 0; }
  .p-1 { padding: ${props => props.theme.spacing.xs}; }
  .p-2 { padding: ${props => props.theme.spacing.sm}; }
  .p-3 { padding: ${props => props.theme.spacing.md}; }
  .p-4 { padding: ${props => props.theme.spacing.lg}; }
  .p-5 { padding: ${props => props.theme.spacing.xl}; }
  
  /* 颜色工具类 */
  .text-primary { color: ${props => props.theme.colors.primary}; }
  .text-secondary { color: ${props => props.theme.colors.secondary}; }
  .text-success { color: ${props => props.theme.colors.success}; }
  .text-danger { color: ${props => props.theme.colors.danger}; }
  .text-warning { color: ${props => props.theme.colors.warning}; }
  .text-info { color: ${props => props.theme.colors.info}; }
  .text-muted { color: ${props => props.theme.colors.textSecondary}; }
  
  .bg-primary { background-color: ${props => props.theme.colors.primary}; }
  .bg-secondary { background-color: ${props => props.theme.colors.secondary}; }
  .bg-success { background-color: ${props => props.theme.colors.success}; }
  .bg-danger { background-color: ${props => props.theme.colors.danger}; }
  .bg-warning { background-color: ${props => props.theme.colors.warning}; }
  .bg-info { background-color: ${props => props.theme.colors.info}; }
  .bg-light { background-color: ${props => props.theme.colors.light}; }
  .bg-dark { background-color: ${props => props.theme.colors.dark}; }
  
  /* 响应式工具类 */
  @media (max-width: 576px) {
    .d-sm-none { display: none; }
    .d-sm-block { display: block; }
    .d-sm-flex { display: flex; }
  }
  
  @media (max-width: 768px) {
    .d-md-none { display: none; }
    .d-md-block { display: block; }
    .d-md-flex { display: flex; }
  }
  
  @media (max-width: 992px) {
    .d-lg-none { display: none; }
    .d-lg-block { display: block; }
    .d-lg-flex { display: flex; }
  }
`

7.3.3 Emotion库详解

Emotion基础用法:

# 安装Emotion
npm install @emotion/react @emotion/styled
npm install @emotion/babel-plugin  # 构建时优化
// Emotion的基础API
import { css, Global, ThemeProvider } from '@emotion/react'
import styled from '@emotion/styled'

// 1. css prop方式
const CssPropButton = props => (
  <button
    css={css`
      padding: 8px 16px;
      border: none;
      border-radius: 4px;
      background-color: ${props.primary ? '#007bff' : '#6c757d'};
      color: white;
      cursor: pointer;
      transition: all 0.2s ease;
      
      &:hover {
        opacity: 0.8;
      }
      
      &:active {
        transform: translateY(1px);
      }
    `}
    {...props}
  >
    {props.children}
  </button>
)

// 2. styled API(类似styled-components)
const EmotionButton = styled.button`
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  font-size: 14px;
  cursor: pointer;
  transition: all 0.2s ease;
  
  /* 动态样式 */
  background-color: ${props => {
    if (props.disabled) return '#6c757d'
    if (props.variant === 'primary') return '#007bff'
    if (props.variant === 'secondary') return '#6c757d'
    if (props.variant === 'success') return '#28a745'
    if (props.variant === 'danger') return '#dc3545'
    return '#6c757d'
  }};
  
  color: ${props => props.variant === 'secondary' ? '#007bff' : 'white'};
  
  /* 条件样式 */
  ${props => props.fullWidth && css`
    width: 100%;
    display: block;
  `}
  
  ${props => props.loading && css`
    cursor: wait;
    opacity: 0.7;
    
    &::after {
      content: '';
      display: inline-block;
      width: 12px;
      height: 12px;
      margin-left: 8px;
      border: 2px solid transparent;
      border-top: 2px solid currentColor;
      border-radius: 50%;
      animation: spin 1s linear infinite;
    }
  `}
  
  &:hover {
    opacity: ${props => props.disabled ? 0.6 : 0.8};
  }
  
  &:active {
    transform: translateY(0);
  }
  
  @keyframes spin {
    to {
      transform: rotate(360deg);
    }
  }
`

// 3. 复合样式
const buttonStyles = {
  primary: css`
    background-color: #007bff;
    color: white;
    
    &:hover {
      background-color: #0056b3;
    }
  `,
  secondary: css`
    background-color: transparent;
    color: #007bff;
    border: 1px solid #007bff;
    
    &:hover {
      background-color: #007bff;
      color: white;
    }
  `,
  danger: css`
    background-color: #dc3545;
    color: white;
    
    &:hover {
      background-color: #c82333;
    }
  `
}

const ComposedButton = styled.button`
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  font-size: 14px;
  cursor: pointer;
  transition: all 0.2s ease;
  
  /* 应用复合样式 */
  ${props => buttonStyles[props.variant]}
  
  /* 尺寸样式 */
  ${props => props.size === 'small' && css`
    padding: 6px 12px;
    font-size: 12px;
  `}
  
  ${props => props.size === 'large' && css`
    padding: 12px 24px;
    font-size: 16px;
  `}
  
  /* 状态样式 */
  ${props => props.disabled && css`
    opacity: 0.6;
    cursor: not-allowed;
    pointer-events: none;
  `}
`

// 4. 主题提供者
const emotionTheme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    success: '#28a745',
    danger: '#dc3545',
    warning: '#ffc107',
    info: '#17a2b8',
    background: '#ffffff',
    surface: '#f8f9fa',
    text: '#212529',
    textSecondary: '#6c757d',
    border: '#dee2e6'
  },
  
  spacing: {
    1: '4px',
    2: '8px',
    3: '16px',
    4: '24px',
    5: '32px',
    6: '48px'
  },
  
  typography: {
    fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
    fontSize: {
      xs: '12px',
      sm: '14px',
      md: '16px',
      lg: '18px',
      xl: '20px',
      '2xl': '24px'
    },
    fontWeight: {
      light: 300,
      normal: 400,
      medium: 500,
      semibold: 600,
      bold: 700
    }
  },
  
  borderRadius: {
    sm: '2px',
    md: '4px',
    lg: '8px',
    xl: '12px',
    full: '50%'
  },
  
  shadows: {
    sm: '0 1px 2px rgba(0, 0, 0, 0.05)',
    md: '0 2px 8px rgba(0, 0, 0, 0.1)',
    lg: '0 8px 24px rgba(0, 0, 0, 0.15)'
  }
}

// 主题化组件
const ThemedCard = styled.div`
  padding: ${props => props.theme.spacing[4]};
  border-radius: ${props => props.theme.borderRadius.lg};
  background-color: ${props => props.theme.colors.surface};
  color: ${props => props.theme.colors.text};
  box-shadow: ${props => props.theme.shadows.md};
  border: 1px solid ${props => props.theme.colors.border};
  
  & > h1, & > h2, & > h3 {
    margin-top: 0;
    color: ${props => props.theme.colors.text};
  }
  
  & > p {
    color: ${props => props.theme.colors.textSecondary};
  }
`

// 5. 全局样式
const EmotionGlobal = () => (
  <Global
    styles={css`
      * {
        box-sizing: border-box;
      }
      
      html {
        font-size: 16px;
        line-height: 1.15;
        -webkit-text-size-adjust: 100%;
      }
      
      body {
        margin: 0;
        font-family: ${props => props.theme.typography.fontFamily};
        font-size: ${props => props.theme.typography.fontSize.md};
        line-height: 1.5;
        color: ${props => props.theme.colors.text};
        background-color: ${props => props.theme.colors.background};
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
      }
      
      /* 滚动条样式 */
      ::-webkit-scrollbar {
        width: 8px;
        height: 8px;
      }
      
      ::-webkit-scrollbar-track {
        background: ${props => props.theme.colors.surface};
      }
      
      ::-webkit-scrollbar-thumb {
        background: ${props => props.theme.colors.border};
        border-radius: 4px;
        
        &:hover {
          background: ${props => props.theme.colors.textSecondary};
        }
      }
      
      /* 焦点样式 */
      :focus {
        outline: 2px solid ${props => props.theme.colors.primary};
        outline-offset: 2px;
      }
    `}
  />
)

Emotion高级特性:

// 1. 样式缓存和优化
import { cache } from '@emotion/css'

// 配置缓存
cache.compat = true

// 2. 动态样式生成
const generateStyles = (props) => {
  const { variant = 'primary', size = 'medium', disabled = false } = props
  
  return css`
    padding: ${size === 'small' ? '6px 12px' : size === 'large' ? '12px 24px' : '8px 16px'};
    border: none;
    border-radius: 4px;
    font-size: ${size === 'small' ? '12px' : size === 'large' ? '16px' : '14px'};
    cursor: ${disabled ? 'not-allowed' : 'pointer'};
    transition: all 0.2s ease;
    opacity: ${disabled ? 0.6 : 1};
    
    ${variant === 'primary' && css`
      background-color: #007bff;
      color: white;
      
      &:hover {
        background-color: #0056b3;
      }
    `}
    
    ${variant === 'secondary' && css`
      background-color: transparent;
      color: #007bff;
      border: 1px solid #007bff;
      
      &:hover {
        background-color: #007bff;
        color: white;
      }
    `}
  `
}

// 3. 样式组合和继承
const baseButtonStyles = css`
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 4px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  outline: none;
  user-select: none;
`

const sizeStyles = {
  small: css`
    padding: 6px 12px;
    font-size: 12px;
    min-height: 28px;
  `,
  medium: css`
    padding: 8px 16px;
    font-size: 14px;
    min-height: 32px;
  `,
  large: css`
    padding: 12px 24px;
    font-size: 16px;
    min-height: 40px;
  `
}

const variantStyles = {
  primary: css`
    background-color: #007bff;
    color: white;
    
    &:hover {
      background-color: #0056b3;
    }
    
    &:focus {
      box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
    }
  `,
  secondary: css`
    background-color: transparent;
    color: #007bff;
    border: 1px solid #007bff;
    
    &:hover {
      background-color: #007bff;
      color: white;
    }
    
    &:focus {
      box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
    }
  `,
  danger: css`
    background-color: #dc3545;
    color: white;
    
    &:hover {
      background-color: #c82333;
    }
    
    &:focus {
      box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.25);
    }
  `
}

const AdvancedButton = styled.button`
  ${baseButtonStyles}
  ${props => sizeStyles[props.size]}
  ${props => variantStyles[props.variant]}
  
  ${props => props.disabled && css`
    opacity: 0.6;
    cursor: not-allowed;
    pointer-events: none;
  `}
  
  ${props => props.fullWidth && css`
    width: 100%;
  `}
  
  ${props => props.loading && css`
    cursor: wait;
    
    &::after {
      content: '';
      display: inline-block;
      width: 12px;
      height: 12px;
      margin-left: 8px;
      border: 2px solid transparent;
      border-top: 2px solid currentColor;
      border-radius: 50%;
      animation: spin 1s linear infinite;
    }
  `}
  
  @keyframes spin {
    to {
      transform: rotate(360deg);
    }
  }
`

// 4. 响应式样式
const breakpoints = {
  sm: '576px',
  md: '768px',
  lg: '992px',
  xl: '1200px'
}

const responsive = (styles) => css`
  ${styles.mobile && css(styles.mobile)}
  
  @media (min-width: ${breakpoints.sm}) {
    ${styles.sm && css(styles.sm)}
  }
  
  @media (min-width: ${breakpoints.md}) {
    ${styles.md && css(styles.md)}
  }
  
  @media (min-width: ${breakpoints.lg}) {
    ${styles.lg && css(styles.lg)}
  }
  
  @media (min-width: ${breakpoints.xl}) {
    ${styles.xl && css(styles.xl)}
  }
`

const ResponsiveButton = styled.button`
  padding: 12px 24px;
  font-size: 16px;
  
  ${responsive({
    mobile: css`
      padding: 8px 16px;
      font-size: 14px;
      width: 100%;
    `,
    md: css`
      padding: 10px 20px;
      font-size: 15px;
      width: auto;
    `
  })}
  
  ${responsive({
    sm: css`
      background-color: #28a745;
    `,
    lg: css`
      background-color: #007bff;
    `
  })}
`

CSS-in-JS为React应用提供了强大的样式解决方案,通过组件化和动态样式实现了高度可维护的样式系统。选择合适的库需要根据项目需求、团队偏好和性能要求来决定。


7.4 预处理器集成

7.4.1 Sass/SCSS集成

Sass在React项目中的配置:

# Create React App (内置Sass支持)
npm install sass

# Vite配置
npm install -D sass

# Webpack配置
npm install -D sass-loader sass

# 附加工具
npm install -D fibers  # 提升编译速度
npm install -D node-sass   # 或使用dart-sass
// styles/variables.scss - 全局变量
// 颜色系统
$colors: (
  primary: #007bff,
  secondary: #6c757d,
  success: #28a745,
  danger: #dc3545,
  warning: #ffc107,
  info: #17a2b8,
  light: #f8f9fa,
  dark: #343a40,
  
  // 中性色
  white: #ffffff,
  gray-100: #f8f9fa,
  gray-200: #e9ecef,
  gray-300: #dee2e6,
  gray-400: #ced4da,
  gray-500: #adb5bd,
  gray-600: #6c757d,
  gray-700: #495057,
  gray-800: #343a40,
  gray-900: #212529,
  black: #000000
);

// 间距系统
$spacing: (
  0: 0,
  1: 4px,
  2: 8px,
  3: 16px,
  4: 24px,
  5: 32px,
  6: 48px,
  8: 64px,
  10: 80px,
  12: 96px
);

// 字体系统
$font-sizes: (
  xs: 12px,
  sm: 14px,
  base: 16px,
  lg: 18px,
  xl: 20px,
  '2xl': 24px,
  '3xl': 30px,
  '4xl': 36px,
  '5xl': 48px,
  '6xl': 64px
);

$font-weights: (
  light: 300,
  normal: 400,
  medium: 500,
  semibold: 600,
  bold: 700
);

// 断点系统
$breakpoints: (
  xs: 0,
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px,
  xxl: 1400px
);

// 圆角系统
$border-radius: (
  none: 0,
  sm: 2px,
  md: 4px,
  lg: 8px,
  xl: 12px,
  '2xl': 16px,
  full: 50%
);

// 阴影系统
$shadows: (
  sm: 0 1px 2px rgba(0, 0, 0, 0.05),
  md: 0 2px 8px rgba(0, 0, 0, 0.1),
  lg: 0 8px 24px rgba(0, 0, 0, 0.15),
  xl: 0 12px 40px rgba(0, 0, 0, 0.2),
  '2xl': 0 20px 60px rgba(0, 0, 0, 0.3)
);

// Z-index层级
$z-index: (
  dropdown: 1000,
  sticky: 1020,
  fixed: 1030,
  modal-backdrop: 1040,
  modal: 1050,
  popover: 1060,
  tooltip: 1070,
  toast: 1080
);
// styles/mixins.scss - 常用混入
// 响应式媒体查询
@mixin respond-to($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    @media (min-width: map-get($breakpoints, $breakpoint)) {
      @content;
    }
  } @else {
    @warn "未定义的断点: #{$breakpoint}";
  }
}

// 按钮基础样式
@mixin button-base {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 8px 16px;
  border: none;
  border-radius: map-get($border-radius, md);
  font-family: inherit;
  font-size: map-get($font-sizes, sm);
  font-weight: map-get($font-weights, medium);
  line-height: 1.5;
  cursor: pointer;
  text-decoration: none;
  transition: all 0.2s ease;
  user-select: none;
  outline: none;
  
  &:focus {
    outline: 2px solid map-get($colors, primary);
    outline-offset: 2px;
  }
  
  &:disabled {
    opacity: 0.6;
    cursor: not-allowed;
    pointer-events: none;
  }
}

// 卡片阴影
@mixin card-shadow($level: md) {
  box-shadow: map-get($shadows, $level);
}

// 文本截断
@mixin text-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

// 多行文本截断
@mixin text-truncate-lines($lines: 2) {
  display: -webkit-box;
  -webkit-line-clamp: $lines;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

// Flex布局
@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

@mixin flex-between {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

// 清除浮动
@mixin clearfix {
  &::after {
    content: '';
    display: table;
    clear: both;
  }
}

// 隐藏滚动条
@mixin hide-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;
  
  &::-webkit-scrollbar {
    display: none;
  }
}

// 渐变背景
@mixin gradient-bg($start-color, $end-color, $direction: to right) {
  background: linear-gradient($direction, $start-color, $end-color);
}

// 动画混入
@mixin fade-in($duration: 0.3s) {
  opacity: 0;
  animation: fadeIn $duration ease-in-out forwards;
}

@keyframes fadeIn {
  to {
    opacity: 1;
  }
}

@mixin slide-up($distance: 20px, $duration: 0.3s) {
  transform: translateY($distance);
  opacity: 0;
  animation: slideUp $duration ease-out forwards;
}

@keyframes slideUp {
  to {
    transform: translateY(0);
    opacity: 1;
  }
}
// styles/components/Button.module.scss - 按钮组件样式
@use '../variables' as *;
@use '../mixins' as *;

.button {
  @include button-base;
  
  // 变体样式
  &--primary {
    background-color: map-get($colors, primary);
    color: white;
    
    &:hover:not(:disabled) {
      background-color: darken(map-get($colors, primary), 10%);
    }
    
    &:active:not(:disabled) {
      transform: translateY(1px);
    }
  }
  
  &--secondary {
    background-color: transparent;
    color: map-get($colors, primary);
    border: 1px solid map-get($colors, primary);
    
    &:hover:not(:disabled) {
      background-color: map-get($colors, primary);
      color: white;
    }
  }
  
  &--danger {
    background-color: map-get($colors, danger);
    color: white;
    
    &:hover:not(:disabled) {
      background-color: darken(map-get($colors, danger), 10%);
    }
  }
  
  &--ghost {
    background-color: transparent;
    color: map-get($colors, gray-700);
    
    &:hover:not(:disabled) {
      background-color: map-get($colors, gray-100);
    }
  }
  
  // 尺寸变体
  &--small {
    padding: 6px 12px;
    font-size: map-get($font-sizes, xs);
    min-height: 28px;
  }
  
  &--large {
    padding: 12px 24px;
    font-size: map-get($font-sizes, base);
    min-height: 40px;
  }
  
  // 全宽
  &--full-width {
    width: 100%;
  }
  
  // 加载状态
  &--loading {
    cursor: wait;
    opacity: 0.7;
    
    &::after {
      content: '';
      display: inline-block;
      width: 12px;
      height: 12px;
      margin-left: 8px;
      border: 2px solid transparent;
      border-top: 2px solid currentColor;
      border-radius: 50%;
      animation: spin 1s linear infinite;
    }
  }
  
  // 图标按钮
  &--icon-only {
    padding: 8px;
    border-radius: 50%;
    width: 36px;
    height: 36px;
    
    &.button--small {
      width: 28px;
      height: 28px;
      padding: 6px;
    }
    
    &.button--large {
      width: 44px;
      height: 44px;
      padding: 12px;
    }
  }
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
// 在React组件中使用Sass Modules
import React from 'react'
import styles from './Button.module.scss'
import { forwardRef } from 'react'

const Button = forwardRef(({
  children,
  variant = 'primary',
  size = 'medium',
  loading = false,
  disabled = false,
  fullWidth = false,
  iconOnly = false,
  className = '',
  onClick,
  ...props
}, ref) => {
  const buttonClasses = [
    styles.button,
    variant && styles[`button--${variant}`],
    size !== 'medium' && styles[`button--${size}`],
    loading && styles['button--loading'],
    fullWidth && styles['button--full-width'],
    iconOnly && styles['button--icon-only'],
    className
  ].filter(Boolean).join(' ')
  
  return (
    <button
      ref={ref}
      className={buttonClasses}
      disabled={disabled || loading}
      onClick={onClick}
      {...props}
    >
      {children}
    </button>
  )
})

Button.displayName = 'Button'

export default Button

7.4.2 Less集成

Less在React项目中的配置:

# 安装Less
npm install -D less less-loader

# Vite配置
# Vite原生支持less,无需额外配置

# Webpack配置
# 需要配置less-loader
// styles/variables.less - Less变量
@primary-color: #007bff;
@secondary-color: #6c757d;
@success-color: #28a745;
@danger-color: #dc3545;
@warning-color: #ffc107;
@info-color: #17a2b8;

@white: #ffffff;
@gray-100: #f8f9fa;
@gray-200: #e9ecef;
@gray-300: #dee2e6;
@gray-400: #ced4da;
@gray-500: #adb5bd;
@gray-600: #6c757d;
@gray-700: #495057;
@gray-800: #343a40;
@gray-900: #212529;
@black: #000000;

// 间距变量
@spacing-xs: 4px;
@spacing-sm: 8px;
@spacing-md: 16px;
@spacing-lg: 24px;
@spacing-xl: 32px;
@spacing-xxl: 48px;

// 字体变量
@font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@font-size-xs: 12px;
@font-size-sm: 14px;
@font-size-base: 16px;
@font-size-lg: 18px;
@font-size-xl: 20px;
@font-size-2xl: 24px;

// 断点变量
@screen-xs: 480px;
@screen-sm: 768px;
@screen-md: 992px;
@screen-lg: 1200px;
@screen-xl: 1600px;

// 圆角变量
@border-radius-sm: 2px;
@border-radius-base: 4px;
@border-radius-lg: 8px;
@border-radius-xl: 12px;

// 阴影变量
@box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.1);
@box-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.15);
@box-shadow-xl: 0 12px 40px rgba(0, 0, 0, 0.2);
// styles/mixins.less - Less混入
// 响应式断点
.respond-to(@breakpoint) when (@breakpoint = xs) {
  @media (max-width: @screen-xs - 1) {
    & {
      @content();
    }
  }
}

.respond-to(@breakpoint) when (@breakpoint = sm) {
  @media (min-width: @screen-xs) and (max-width: @screen-sm - 1) {
    & {
      @content();
    }
  }
}

.respond-to(@breakpoint) when (@breakpoint = md) {
  @media (min-width: @screen-sm) and (max-width: @screen-md - 1) {
    & {
      @content();
    }
  }
}

.respond-to(@breakpoint) when (@breakpoint = lg) {
  @media (min-width: @screen-md) and (max-width: @screen-lg - 1) {
    & {
      @content();
    }
  }
}

.respond-to(@breakpoint) when (@breakpoint = xl) {
  @media (min-width: @screen-lg) {
    & {
      @content();
    }
  }
}

// 按钮基础样式
.button-base() {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: @spacing-sm @spacing-md;
  border: none;
  border-radius: @border-radius-base;
  font-family: @font-family-base;
  font-size: @font-size-sm;
  font-weight: 500;
  line-height: 1.5;
  cursor: pointer;
  transition: all 0.2s ease;
  user-select: none;
  outline: none;
  
  &:focus {
    outline: 2px solid @primary-color;
    outline-offset: 2px;
  }
  
  &:disabled {
    opacity: 0.6;
    cursor: not-allowed;
    pointer-events: none;
  }
}

// 卡片样式
.card-base() {
  padding: @spacing-lg;
  border-radius: @border-radius-lg;
  background-color: @white;
  box-shadow: @box-shadow-base;
  border: 1px solid @gray-200;
  
  &:hover {
    box-shadow: @box-shadow-lg;
  }
}

// 动画
.fade-in(@duration: 0.3s) {
  opacity: 0;
  animation: fadeIn @duration ease-in-out forwards;
}

@keyframes fadeIn {
  to {
    opacity: 1;
  }
}

.slide-up(@distance: 20px, @duration: 0.3s) {
  transform: translateY(@distance);
  opacity: 0;
  animation: slideUp @duration ease-out forwards;
}

@keyframes slideUp {
  to {
    transform: translateY(0);
    opacity: 1;
  }
}
// styles/components/Card.module.less - 卡片组件
@import '../variables';
@import '../mixins';

.card {
  .card-base();
  
  // 变体
  &--elevated {
    box-shadow: @box-shadow-lg;
    transform: translateY(-2px);
    
    &:hover {
      box-shadow: @box-shadow-xl;
      transform: translateY(-4px);
    }
  }
  
  &--bordered {
    box-shadow: none;
    border: 2px solid @gray-300;
    
    &:hover {
      border-color: @primary-color;
    }
  }
  
  &--transparent {
    background-color: transparent;
    box-shadow: none;
    border: 1px solid @gray-200;
  }
  
  // 尺寸变体
  &--small {
    padding: @spacing-sm;
    border-radius: @border-radius-sm;
  }
  
  &--large {
    padding: @spacing-xl;
    border-radius: @border-radius-xl;
  }
  
  // 状态
  &--loading {
    position: relative;
    
    &::after {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: rgba(255, 255, 255, 0.8);
      display: flex;
      align-items: center;
      justify-content: center;
    }
  }
  
  &--error {
    border-color: @danger-color;
    background-color: lighten(@danger-color, 40%);
    
    .card__title {
      color: @danger-color;
    }
  }
  
  // 响应式
  .respond-to(sm, {
    padding: @spacing-md;
  });
  
  .respond-to(xs, {
    padding: @spacing-sm;
    margin: @spacing-xs;
  });
}

.card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: @spacing-md;
  
  & + .card__content {
    padding-top: 0;
  }
}

.card__title {
  font-size: @font-size-lg;
  font-weight: 600;
  color: @gray-800;
  margin: 0;
}

.card__subtitle {
  font-size: @font-size-sm;
  color: @gray-600;
  margin-top: @spacing-xs;
}

.card__content {
  color: @gray-700;
  line-height: 1.6;
}

.card__footer {
  margin-top: @spacing-lg;
  padding-top: @spacing-md;
  border-top: 1px solid @gray-200;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  
  & > * {
    margin-left: @spacing-sm;
    
    &:first-child {
      margin-left: 0;
    }
  }
}

// 动作卡片
.card__actions {
  display: flex;
  align-items: center;
  
  &--left {
    justify-content: flex-start;
  }
  
  &--center {
    justify-content: center;
  }
  
  &--right {
    justify-content: flex-end;
  }
  
  &--spaced {
    justify-content: space-between;
  }
}

7.4.3 PostCSS集成

PostCSS配置和插件:

# 安装PostCSS和常用插件
npm install -D postcss
npm install -D postcss-preset-env  # 现代CSS特性支持
npm install -D autoprefixer        # 自动添加浏览器前缀
npm install -D cssnano            # CSS压缩和优化
npm install -D postcss-import     # @import支持
npm install -D postcss-nested     # 嵌套语法支持
npm install -D postcss-pxtorem    # px转rem
npm install -D postcss-viewport-units # 视口单位支持
// postcss.config.js
module.exports = {
  plugins: [
    // @import支持
    'postcss-import',
    
    // 嵌套语法支持
    'postcss-nested',
    
    // 预设环境 - 自动处理现代CSS特性
    'postcss-preset-env({
      stage: 3, // 支持第3阶段的CSS特性
      features: {
        'nesting-rules': true,
        'custom-properties': true,
        'custom-media-queries': true
      },
      autoprefixer: {
        grid: true // 支持CSS Grid
      }
    })`,
    
    // px转rem配置
    'postcss-pxtorem({
      rootValue: 16, // 基准值
      unitPrecision: 5, // 精度
      selectorBlackList: ['.ignore'], // 忽略选择器
      propList: ['*', '!border*'], // 转换属性,忽略边框
      replace: true,
      mediaQuery: false, // 媒体查询中的px不转换
      minPixelValue: 1 // 最小转换值
    })',
    
    // 视口单位支持
    'postcss-viewport-units',
    
    // 生产环境压缩
    ...(process.env.NODE_ENV === 'production' ? [
      'cssnano({
        preset: [
          'default',
          {
            discardComments: { removeAll: true },
            normalizeWhitespace: true,
            minifySelectors: true,
            minifyParams: true
          }
        ]
      })'
    ] : [])
  ]
}
/* styles/main.css - 使用PostCSS特性 */
/* 自定义属性(CSS变量) */
:root {
  --primary-color: #007bff;
  --secondary-color: #6c757d;
  --success-color: #28a745;
  --danger-color: #dc3545;
  --warning-color: #ffc107;
  --info-color: #17a2b8;
  
  --font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-size-base: 16px;
  --line-height-base: 1.5;
  
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  
  --border-radius-sm: 2px;
  --border-radius-base: 4px;
  --border-radius-lg: 8px;
  --border-radius-xl: 12px;
}

/* 嵌套语法(通过postcss-nested) */
.card {
  padding: var(--spacing-lg);
  border-radius: var(--border-radius-lg);
  background-color: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  border: 1px solid #e5e7eb;
  
  &__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--spacing-md);
    
    &--bordered {
      border-bottom: 1px solid #e5e7eb;
      padding-bottom: var(--spacing-md);
      margin-bottom: 0;
    }
  }
  
  &__title {
    font-size: 18px;
    font-weight: 600;
    color: #1f2937;
    margin: 0;
  }
  
  &__content {
    color: #4b5563;
    line-height: var(--line-height-base);
  }
  
  &__footer {
    margin-top: var(--spacing-lg);
    padding-top: var(--spacing-md);
    border-top: 1px solid #e5e7eb;
    display: flex;
    align-items: center;
    justify-content: flex-end;
  }
  
  /* 现代CSS特性 */
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  }
  
  &:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
  }
  
  /* 自定义媒体查询(CSS Media Queries Level 4) */
  @media (min-width: 768px) {
    padding: var(--spacing-xl);
  }
}

/* 现代CSS特性示例 */
.modern-button {
  /* CSS自定义属性与计算 */
  --button-bg: var(--primary-color);
  --button-text: white;
  --button-hover: color-mix(in srgb, var(--button-bg) 85%, black);
  
  padding: var(--spacing-sm) var(--spacing-md);
  border: none;
  border-radius: var(--border-radius-base);
  background-color: var(--button-bg);
  color: var(--button-text);
  font-family: var(--font-family-base);
  font-size: var(--font-size-base);
  cursor: pointer;
  transition: all 0.2s ease;
  
  /* 现代选择器 */
  &:where(:hover) {
    background-color: var(--button-hover);
  }
  
  &:where(:focus-visible) {
    outline: 2px solid var(--button-bg);
    outline-offset: 2px;
  }
  
  /* 容器查询 */
  @container (min-width: 300px) {
    font-size: 18px;
    padding: var(--spacing-md) var(--spacing-lg);
  }
}

/* 响应式设计 */
.responsive-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--spacing-md);
  
  /* 使用clamp函数 */
  padding: clamp(1rem, 4vw, 2rem);
  
  /* 现代间距 */
  margin-inline: auto;
  max-width: 1200px;
}

/* 现代布局 */
.flexbox-layout {
  display: flex;
  gap: var(--spacing-md);
  
  & > * {
    flex: 1;
    min-width: 0; /* 防止flex子元素溢出 */
  }
}

/* 现代动画 */
@keyframes slideInFromLeft {
  from {
    opacity: 0;
    transform: translateX(-100%);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

.slide-in-animation {
  animation: slideInFromLeft 0.3s ease-out;
  
  /* 使用view()函数控制动画 */
  animation-timeline: view();
  animation-range: entry 0% cover 20%;
}
// React中使用PostCSS
import React from 'react'
import './styles/main.css'
import './styles/components.css'

function ModernCard({ title, children, actions }) {
  return (
    <article className="card">
      <header className="card__header">
        <h2 className="card__title">{title}</h2>
        {actions && (
          <div className="card__actions">
            {actions}
          </div>
        )}
      </header>
      
      <div className="card__content">
        {children}
      </div>
      
      <footer className="card__footer">
        <button className="modern-button">
          查看详情
        </button>
      </footer>
    </article>
  )
}

function App() {
  return (
    <main className="responsive-grid">
      <ModernCard 
        title="现代CSS特性"
        actions={<button className="modern-button">编辑</button>}
      >
        <p>这个卡片展示了PostCSS处理的各种现代CSS特性。</p>
      </ModernCard>
      
      <ModernCard title="响应式设计">
        <p>使用现代CSS Grid和Flexbox实现响应式布局。</p>
      </ModernCard>
    </main>
  )
}

export default App

通过集成CSS预处理器,开发者可以使用更强大的CSS特性,如变量、嵌套、混入等,大大提升了CSS的可维护性和开发效率。PostCSS作为后处理器,可以自动处理浏览器兼容性和优化代码,是现代前端开发的必备工具。


7.5 组件库和设计系统

7.5.1 主流UI组件库选择

React UI组件库对比:

// 1. Ant Design - 企业级UI设计语言
import { Button, Table, Form, Modal, ConfigProvider } from 'antd'
import zhCN from 'antd/locale/zh_CN'

// Ant Design特点
const antdFeatures = {
  description: '企业级UI设计语言和组件库',
  advantages: [
    '丰富的组件库,覆盖企业应用场景',
    '完善的设计规范和设计语言',
    '强大的Table、Form、Modal等企业级组件',
    '国际化支持完善',
    'TypeScript支持良好',
    '社区活跃,文档详细'
  ],
  disadvantages: [
    '包体积较大',
    '定制化程度有限',
    '设计风格较为固化'
  ],
  bestFor: ['企业级应用', '后台管理系统', '数据展示平台']
}

// Ant Design使用示例
function AntdExample() {
  const [form] = Form.useForm()
  const [visible, setVisible] = useState(false)
  
  const columns = [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name',
    },
    {
      title: '年龄',
      dataIndex: 'age',
      key: 'age',
    },
    {
      title: '地址',
      dataIndex: 'address',
      key: 'address',
    },
  ]
  
  const data = [
    {
      key: '1',
      name: '张三',
      age: 32,
      address: '北京市朝阳区',
    },
    {
      key: '2',
      name: '李四',
      age: 28,
      address: '上海市浦东新区',
    },
  ]
  
  return (
    <ConfigProvider locale={zhCN}>
      <div>
        <Form form={form} layout="inline">
          <Form.Item name="username" rules={[{ required: true }]}>
            <Input placeholder="用户名" />
          </Form.Item>
          <Form.Item>
            <Button type="primary" onClick={() => setVisible(true)}>
              新增用户
            </Button>
          </Form.Item>
        </Form>
        
        <Table 
          columns={columns} 
          dataSource={data} 
          pagination={{ pageSize: 10 }}
        />
        
        <Modal
          title="新增用户"
          open={visible}
          onOk={() => setVisible(false)}
          onCancel={() => setVisible(false)}
        >
          <p>这是一个Ant Design的模态框</p>
        </Modal>
      </div>
    </ConfigProvider>
  )
}
// 2. Material-UI (MUI) - Material Design组件库
import {
  Button,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  TextField,
  Dialog,
  DialogTitle,
  DialogContent,
  DialogActions,
  ThemeProvider,
  createTheme
} from '@mui/material'

// MUI特点
const muiFeatures = {
  description: 'React的Material Design组件库',
  advantages: [
    '遵循Google Material Design设计规范',
    '组件设计现代化,交互体验好',
    '强大的主题系统',
    '丰富的图标库',
    '优秀的TypeScript支持',
    '组件可定制性强'
  ],
  disadvantages: [
    '学习曲线较陡',
    '包体积较大',
    '某些组件性能需要优化'
  ],
  bestFor: ['现代化应用', '移动端应用', '设计驱动的项目']
}

// MUI主题配置
const customTheme = createTheme({
  palette: {
    primary: {
      main: '#1976d2',
      light: '#42a5f5',
      dark: '#1565c0',
    },
    secondary: {
      main: '#dc004e',
      light: '#ff5983',
      dark: '#9a0036',
    },
    background: {
      default: '#fafafa',
      paper: '#ffffff',
    },
  },
  typography: {
    fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
    h1: {
      fontSize: '2.125rem',
      fontWeight: 300,
    },
  },
  shape: {
    borderRadius: 8,
  },
})

// MUI使用示例
function MuiExample() {
  const [open, setOpen] = useState(false)
  const [name, setName] = useState('')
  
  const handleClickOpen = () => {
    setOpen(true)
  }
  
  const handleClose = () => {
    setOpen(false)
  }
  
  return (
    <ThemeProvider theme={customTheme}>
      <div>
        <TextField
          label="用户名"
          variant="outlined"
          value={name}
          onChange={(e) => setName(e.target.value)}
          style={{ marginRight: 16 }}
        />
        <Button 
          variant="contained" 
          color="primary" 
          onClick={handleClickOpen}
        >
          新增用户
        </Button>
        
        <TableContainer>
          <Table>
            <TableHead>
              <TableRow>
                <TableCell>姓名</TableCell>
                <TableCell>年龄</TableCell>
                <TableCell>地址</TableCell>
              </TableRow>
            </TableHead>
            <TableBody>
              <TableRow>
                <TableCell>张三</TableCell>
                <TableCell>32</TableCell>
                <TableCell>北京市朝阳区</TableCell>
              </TableRow>
              <TableRow>
                <TableCell>李四</TableCell>
                <TableCell>28</TableCell>
                <TableCell>上海市浦东新区</TableCell>
              </TableRow>
            </TableBody>
          </Table>
        </TableContainer>
        
        <Dialog open={open} onClose={handleClose}>
          <DialogTitle>新增用户</DialogTitle>
          <DialogContent>
            <TextField
              autoFocus
              margin="dense"
              label="用户名"
              fullWidth
              variant="standard"
            />
          </DialogContent>
          <DialogActions>
            <Button onClick={handleClose}>取消</Button>
            <Button onClick={handleClose}>确认</Button>
          </DialogActions>
        </Dialog>
      </div>
    </ThemeProvider>
  )
}
// 3. Chakra UI - 简洁现代的组件库
import {
  Button,
  Table,
  Thead,
  Tbody,
  Tr,
  Th,
  Td,
  Input,
  Modal,
  ModalOverlay,
  ModalContent,
  ModalHeader,
  ModalFooter,
  ModalBody,
  ModalCloseButton,
  useDisclosure,
  ChakraProvider,
  extendTheme
} from '@chakra-ui/react'

// Chakra UI特点
const chakraFeatures = {
  description: '简单、模块化、可访问的React组件库',
  advantages: [
    '简洁的API设计',
    '强大的样式系统',
    '优秀的可访问性支持',
    '轻量级,模块化导入',
    '内置深色模式',
    '动画和过渡效果出色'
  ],
  disadvantages: [
    '组件数量相对较少',
    '生态系统还在发展中',
    '某些高级功能需要自行实现'
  ],
  bestFor: ['现代Web应用', '快速原型开发', '注重用户体验的项目']
}

// Chakra UI主题扩展
const customTheme = extendTheme({
  colors: {
    brand: {
      50: '#e3f2fd',
      100: '#bbdefb',
      500: '#2196f3',
      600: '#1e88e5',
      700: '#1976d2',
      900: '#0d47a1',
    },
  },
  fonts: {
    heading: '"Inter", sans-serif',
    body: '"Inter", sans-serif',
  },
  components: {
    Button: {
      baseStyle: {
        fontWeight: 'medium',
      },
      variants: {
        solid: {
          bg: 'brand.500',
          color: 'white',
          _hover: {
            bg: 'brand.600',
          },
        },
      },
    },
  },
})

// Chakra UI使用示例
function ChakraExample() {
  const { isOpen, onOpen, onClose } = useDisclosure()
  const [name, setName] = useState('')
  
  return (
    <ChakraProvider theme={customTheme}>
      <div>
        <Input
          placeholder="用户名"
          value={name}
          onChange={(e) => setName(e.target.value)}
          mr={4}
          w="200px"
        />
        <Button colorScheme="brand" onClick={onOpen}>
          新增用户
        </Button>
        
        <Table variant="simple" mt={8}>
          <Thead>
            <Tr>
              <Th>姓名</Th>
              <Th>年龄</Th>
              <Th>地址</Th>
            </Tr>
          </Thead>
          <Tbody>
            <Tr>
              <Td>张三</Td>
              <Td>32</Td>
              <Td>北京市朝阳区</Td>
            </Tr>
            <Tr>
              <Td>李四</Td>
              <Td>28</Td>
              <Td>上海市浦东新区</Td>
            </Tr>
          </Tbody>
        </Table>
        
        <Modal isOpen={isOpen} onClose={onClose}>
          <ModalOverlay />
          <ModalContent>
            <ModalHeader>新增用户</ModalHeader>
            <ModalCloseButton />
            <ModalBody>
              <Input placeholder="请输入用户名" />
            </ModalBody>
            <ModalFooter>
              <Button colorScheme="blue" mr={3} onClick={onClose}>
                关闭
              </Button>
              <Button variant="ghost">确认</Button>
            </ModalFooter>
          </ModalContent>
        </Modal>
      </div>
    </ChakraProvider>
  )
}

7.5.2 组件库定制和主题化

Ant Design主题定制:

// Ant Design主题配置
import { ConfigProvider, theme } from 'antd'
import { useToken } from 'antd/es/theme/internal'

// 1. 使用ConfigProvider进行主题定制
function AntdThemeCustomization() {
  const customTheme = {
    algorithm: theme.darkAlgorithm, // 深色主题
    token: {
      colorPrimary: '#1890ff',
      colorSuccess: '#52c41a',
      colorWarning: '#faad14',
      colorError: '#f5222d',
      colorInfo: '#1890ff',
      
      // 字体
      fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',
      fontSize: 14,
      borderRadius: 8,
      
      // 间距
      padding: 16,
      paddingLG: 24,
      paddingSM: 12,
      paddingXS: 8,
      
      // 控制台
      controlHeight: 40,
      controlHeightLG: 48,
      controlHeightSM: 32,
      
      // 阴影
      boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
      boxShadowSecondary: '0 4px 12px rgba(0, 0, 0, 0.15)',
    },
    components: {
      Button: {
        colorPrimary: '#1890ff',
        algorithm: true, // 启用算法派生
        controlHeight: 40,
      },
      Input: {
        colorPrimary: '#1890ff',
        algorithm: true,
      },
      Table: {
        colorBgContainer: '#ffffff',
        colorBorder: '#f0f0f0',
        headerBg: '#fafafa',
      },
      Modal: {
        contentBg: '#ffffff',
        headerBg: '#ffffff',
        footerBg: '#ffffff',
      },
    },
  }
  
  return (
    <ConfigProvider theme={customTheme}>
      <App />
    </ConfigProvider>
  )
}

// 2. 使用useToken获取主题令牌
function CustomButton() {
  const { token } = useToken()
  
  return (
    <button
      style={{
        backgroundColor: token.colorPrimary,
        color: token.colorWhite,
        border: `1px solid ${token.colorPrimary}`,
        borderRadius: token.borderRadius,
        padding: `${token.paddingSM}px ${token.padding}px`,
        fontSize: token.fontSize,
        fontFamily: token.fontFamily,
        cursor: 'pointer',
        transition: 'all 0.2s',
      }}
    >
      自定义按钮
    </button>
  )
}

// 3. 动态主题切换
function ThemeSwitcher() {
  const [isDark, setIsDark] = useState(false)
  const [primaryColor, setPrimaryColor] = useState('#1890ff')
  
  const theme = {
    algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm,
    token: {
      colorPrimary: primaryColor,
      borderRadius: 8,
    },
    components: {
      Button: {
        algorithm: true,
      },
    },
  }
  
  return (
    <ConfigProvider theme={theme}>
      <div>
        <div style={{ marginBottom: 20 }}>
          <label>
            主题:
            <select
              value={isDark ? 'dark' : 'light'}
              onChange={(e) => setIsDark(e.target.value === 'dark')}
            >
              <option value="light">浅色</option>
              <option value="dark">深色</option>
            </select>
          </label>
          
          <label style={{ marginLeft: 20 }}>
            主色调:
            <input
              type="color"
              value={primaryColor}
              onChange={(e) => setPrimaryColor(e.target.value)}
            />
          </label>
        </div>
        
        <Button type="primary">主题按钮</Button>
        <Button>默认按钮</Button>
        <Button type="dashed">虚线按钮</Button>
      </div>
    </ConfigProvider>
  )
}

MUI深度定制:

import { createTheme, ThemeProvider, CssBaseline } from '@mui/material'

// 1. 完整主题配置
const muiCustomTheme = createTheme({
  palette: {
    mode: 'light', // 'light' | 'dark'
    primary: {
      main: '#1976d2',
      light: '#42a5f5',
      dark: '#1565c0',
      contrastText: '#ffffff',
    },
    secondary: {
      main: '#dc004e',
      light: '#ff5983',
      dark: '#9a0036',
      contrastText: '#ffffff',
    },
    error: {
      main: '#d32f2f',
      light: '#ef5350',
      dark: '#c62828',
    },
    warning: {
      main: '#ed6c02',
      light: '#ff9800',
      dark: '#e65100',
    },
    info: {
      main: '#0288d1',
      light: '#03a9f4',
      dark: '#01579b',
    },
    success: {
      main: '#2e7d32',
      light: '#4caf50',
      dark: '#1b5e20',
    },
    background: {
      default: '#fafafa',
      paper: '#ffffff',
    },
    text: {
      primary: 'rgba(0, 0, 0, 0.87)',
      secondary: 'rgba(0, 0, 0, 0.6)',
      disabled: 'rgba(0, 0, 0, 0.38)',
    },
    divider: 'rgba(0, 0, 0, 0.12)',
  },
  
  typography: {
    fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
    fontSize: 14,
    h1: {
      fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
      fontSize: '2.125rem',
      fontWeight: 300,
      lineHeight: 1.167,
    },
    h2: {
      fontSize: '1.5rem',
      fontWeight: 400,
      lineHeight: 1.2,
    },
    h3: {
      fontSize: '1.25rem',
      fontWeight: 500,
      lineHeight: 1.167,
    },
    body1: {
      fontSize: '1rem',
      lineHeight: 1.5,
    },
    button: {
      fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
      fontWeight: 500,
      textTransform: 'none',
    },
  },
  
  shape: {
    borderRadius: 8,
  },
  
  spacing: 8,
  
  breakpoints: {
    values: {
      xs: 0,
      sm: 600,
      md: 960,
      lg: 1280,
      xl: 1920,
    },
  },
  
  components: {
    // 自定义Button组件
    MuiButton: {
      styleOverrides: {
        root: {
          textTransform: 'none',
          borderRadius: 8,
          fontWeight: 500,
          padding: '8px 16px',
          boxShadow: 'none',
          '&:hover': {
            boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
          },
        },
        containedPrimary: {
          background: 'linear-gradient(45deg, #1976d2, #42a5f5)',
          '&:hover': {
            background: 'linear-gradient(45deg, #1565c0, #2196f3)',
          },
        },
      },
    },
    
    // 自定义Card组件
    MuiCard: {
      styleOverrides: {
        root: {
          borderRadius: 12,
          boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
          border: '1px solid rgba(0, 0, 0, 0.08)',
          '&:hover': {
            boxShadow: '0 8px 24px rgba(0, 0, 0, 0.15)',
            transform: 'translateY(-2px)',
          },
        },
      },
    },
    
    // 自定义TextField组件
    MuiTextField: {
      styleOverrides: {
        root: {
          '& .MuiOutlinedInput-root': {
            borderRadius: 8,
            '&:hover .MuiOutlinedInput-notchedOutline': {
              borderColor: 'rgba(0, 0, 0, 0.23)',
            },
            '&.Mui-focused .MuiOutlinedInput-notchedOutline': {
              borderWidth: 2,
            },
          },
        },
      },
    },
  },
})

// 2. 主题切换组件
function MuiThemeSwitcher() {
  const [mode, setMode] = useState('light')
  const [primaryColor, setPrimaryColor] = useState('#1976d2')
  
  const theme = useMemo(() => createTheme({
    ...muiCustomTheme,
    palette: {
      ...muiCustomTheme.palette,
      mode,
      primary: {
        ...muiCustomTheme.palette.primary,
        main: primaryColor,
      },
    },
  }), [mode, primaryColor])
  
  const toggleColorMode = () => {
    setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'))
  }
  
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <Box sx={{ p: 3 }}>
        <Box sx={{ mb: 3, display: 'flex', gap: 2, alignItems: 'center' }}>
          <Typography>主题模式:</Typography>
          <Button
            variant={mode === 'light' ? 'contained' : 'outlined'}
            onClick={() => setMode('light')}
          >
            浅色
          </Button>
          <Button
            variant={mode === 'dark' ? 'contained' : 'outlined'}
            onClick={() => setMode('dark')}
          >
            深色
          </Button>
          
          <Typography sx={{ ml: 3 }}>主色调:</Typography>
          <input
            type="color"
            value={primaryColor}
            onChange={(e) => setPrimaryColor(e.target.value)}
            style={{ width: 40, height: 40 }}
          />
        </Box>
        
        <Card sx={{ p: 3, mb: 2 }}>
          <Typography variant="h6" gutterBottom>
            主题演示卡片
          </Typography>
          <Typography variant="body2" color="text.secondary">
            这是一个使用自定义主题的卡片组件
          </Typography>
          <Box sx={{ mt: 2, display: 'flex', gap: 2 }}>
            <Button variant="contained" color="primary">
              主要按钮
            </Button>
            <Button variant="outlined" color="secondary">
              次要按钮
            </Button>
          </Box>
        </Card>
        
        <TextField
          label="输入框示例"
          variant="outlined"
          fullWidth
          sx={{ mt: 2 }}
        />
      </Box>
    </ThemeProvider>
  )
}

7.5.3 设计令牌系统

设计令牌(Design Tokens)概念:

// tokens/design-tokens.js - 设计令牌定义
export const colorTokens = {
  // 基础颜色
  core: {
    white: '#FFFFFF',
    black: '#000000',
    
    // 品牌色
    blue: {
      50: '#EFF6FF',
      100: '#DBEAFE',
      200: '#BFDBFE',
      300: '#93C5FD',
      400: '#60A5FA',
      500: '#3B82F6', // 主色
      600: '#2563EB',
      700: '#1D4ED8',
      800: '#1E40AF',
      900: '#1E3A8A',
    },
    
    gray: {
      50: '#F9FAFB',
      100: '#F3F4F6',
      200: '#E5E7EB',
      300: '#D1D5DB',
      400: '#9CA3AF',
      500: '#6B7280',
      600: '#4B5563',
      700: '#374151',
      800: '#1F2937',
      900: '#111827',
    },
    
    green: {
      50: '#ECFDF5',
      100: '#D1FAE5',
      200: '#A7F3D0',
      300: '#6EE7B7',
      400: '#34D399',
      500: '#10B981',
      600: '#059669',
      700: '#047857',
      800: '#065F46',
      900: '#064E3B',
    },
    
    red: {
      50: '#FEF2F2',
      100: '#FEE2E2',
      200: '#FECACA',
      300: '#FCA5A5',
      400: '#F87171',
      500: '#EF4444',
      600: '#DC2626',
      700: '#B91C1C',
      800: '#991B1B',
      900: '#7F1D1D',
    },
  },
  
  // 语义化颜色
  semantic: {
    // 文本颜色
    text: {
      primary: '#111827',
      secondary: '#6B7280',
      tertiary: '#9CA3AF',
      inverse: '#FFFFFF',
      disabled: '#D1D5DB',
    },
    
    // 背景颜色
    background: {
      primary: '#FFFFFF',
      secondary: '#F9FAFB',
      tertiary: '#F3F4F6',
      inverse: '#1F2937',
      overlay: 'rgba(0, 0, 0, 0.5)',
    },
    
    // 边框颜色
    border: {
      primary: '#E5E7EB',
      secondary: '#D1D5DB',
      focus: '#3B82F6',
      error: '#EF4444',
      warning: '#F59E0B',
      success: '#10B981',
    },
    
    // 状态颜色
    status: {
      info: '#3B82F6',
      success: '#10B981',
      warning: '#F59E0B',
      error: '#EF4444',
    },
  },
}

export const spacingTokens = {
  // 基础间距
  base: 4, // 1单位 = 4px
  
  // 间距值
  values: {
    0: 0,
    1: 4,   // 4px
    2: 8,   // 8px
    3: 12,  // 12px
    4: 16,  // 16px
    5: 20,  // 20px
    6: 24,  // 24px
    8: 32,  // 32px
    10: 40, // 40px
    12: 48, // 48px
    16: 64, // 64px
    20: 80, // 80px
    24: 96, // 96px
  },
  
  // 语义化间距
  semantic: {
    xs: 4,
    sm: 8,
    md: 16,
    lg: 24,
    xl: 32,
    '2xl': 48,
    '3xl': 64,
  },
}

export const typographyTokens = {
  // 字体族
  fontFamilies: {
    sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
    serif: ['Georgia', 'serif'],
    mono: ['JetBrains Mono', 'Consolas', 'Monaco', 'monospace'],
  },
  
  // 字体大小
  fontSizes: {
    xs: '12px',
    sm: '14px',
    base: '16px',
    lg: '18px',
    xl: '20px',
    '2xl': '24px',
    '3xl': '30px',
    '4xl': '36px',
    '5xl': '48px',
    '6xl': '64px',
  },
  
  // 字重
  fontWeights: {
    light: 300,
    normal: 400,
    medium: 500,
    semibold: 600,
    bold: 700,
  },
  
  // 行高
  lineHeights: {
    tight: 1.25,
    normal: 1.5,
    relaxed: 1.75,
  },
  
  // 字母间距
  letterSpacings: {
    tighter: '-0.05em',
    tight: '-0.025em',
    normal: '0em',
    wide: '0.025em',
    wider: '0.05em',
    widest: '0.1em',
  },
}

export const effectTokens = {
  // 圆角
  borderRadius: {
    none: '0px',
    sm: '2px',
    base: '4px',
    md: '6px',
    lg: '8px',
    xl: '12px',
    '2xl': '16px',
    '3xl': '24px',
    full: '50%',
  },
  
  // 阴影
  shadows: {
    sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
    base: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
    md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
    lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
    xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
    '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
    inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
  },
  
  // 过渡动画
  transitions: {
    fast: '150ms cubic-bezier(0.4, 0, 0.2, 1)',
    base: '200ms cubic-bezier(0.4, 0, 0.2, 1)',
    slow: '300ms cubic-bezier(0.4, 0, 0.2, 1)',
  },
}
// 使用设计令牌构建主题系统
import React, { createContext, useContext, useMemo } from 'react'
import { colorTokens, spacingTokens, typographyTokens, effectTokens } from './tokens'

// 令牌转换器
export const tokenUtils = {
  // 获取颜色值
  color: (path, alpha = 1) => {
    const keys = path.split('.')
    let value = colorTokens
    
    for (const key of keys) {
      value = value?.[key]
    }
    
    if (!value) {
      console.warn(`未找到颜色令牌: ${path}`)
      return '#000000'
    }
    
    if (alpha < 1) {
      // 转换hex到rgba
      const hex = value.replace('#', '')
      const r = parseInt(hex.substr(0, 2), 16)
      const g = parseInt(hex.substr(2, 2), 16)
      const b = parseInt(hex.substr(4, 2), 16)
      return `rgba(${r}, ${g}, ${b}, ${alpha})`
    }
    
    return value
  },
  
  // 获取间距值
  spacing: (value) => {
    if (typeof value === 'string') {
      return spacingTokens.semantic[value] || spacingTokens.values[value] || value
    }
    if (typeof value === 'number') {
      return spacingTokens.values[value] || value
    }
    return value
  },
  
  // 获取字体大小
  fontSize: (size) => {
    return typographyTokens.fontSizes[size] || size
  },
  
  // 获取字体族
  fontFamily: (type) => {
    const family = typographyTokens.fontFamilies[type]
    return Array.isArray(family) ? family.join(', ') : family
  },
  
  // 获取圆角
  borderRadius: (size) => {
    return effectTokens.borderRadius[size] || size
  },
  
  // 获取阴影
  shadow: (size) => {
    return effectTokens.shadows[size] || size
  },
  
  // 获取过渡
  transition: (speed) => {
    return effectTokens.transitions[speed] || speed
  },
}

// 主题提供者
const ThemeContext = createContext()

export function ThemeProvider({ children, theme = 'light' }) {
  const themeValue = useMemo(() => {
    return {
      mode: theme,
      tokens: {
        colors: colorTokens,
        spacing: spacingTokens,
        typography: typographyTokens,
        effects: effectTokens,
      },
      utils: tokenUtils,
    }
  }, [theme])
  
  return (
    <ThemeContext.Provider value={themeValue}>
      {children}
    </ThemeContext.Provider>
  )
}

export function useTheme() {
  const context = useContext(ThemeContext)
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider')
  }
  return context
}

// 使用设计令牌的组件
function TokenButton({ variant = 'primary', size = 'md', children, ...props }) {
  const { utils } = useTheme()
  
  const buttonStyles = {
    // 基础样式
    padding: `${utils.spacing(2)} ${utils.spacing(4)}`,
    borderRadius: utils.borderRadius('base'),
    fontFamily: utils.fontFamily('sans'),
    fontSize: utils.fontSize('sm'),
    fontWeight: typographyTokens.fontWeights.medium,
    cursor: 'pointer',
    border: 'none',
    transition: utils.transition('base'),
    outline: 'none',
    
    // 变体样式
    ...(variant === 'primary' && {
      backgroundColor: utils.color('core.blue.500'),
      color: utils.color('core.white'),
      border: `1px solid ${utils.color('core.blue.500')}`,
      
      '&:hover': {
        backgroundColor: utils.color('core.blue.600'),
        borderColor: utils.color('core.blue.600'),
      },
      
      '&:focus': {
        boxShadow: `0 0 0 3px ${utils.color('core.blue.500', 0.2)}`,
      },
    }),
    
    ...(variant === 'secondary' && {
      backgroundColor: 'transparent',
      color: utils.color('core.blue.500'),
      border: `1px solid ${utils.color('core.blue.500')}`,
      
      '&:hover': {
        backgroundColor: utils.color('core.blue.50'),
      },
    }),
    
    ...(variant === 'outline' && {
      backgroundColor: 'transparent',
      color: utils.color('semantic.text.primary'),
      border: `1px solid ${utils.color('semantic.border.primary')}`,
      
      '&:hover': {
        backgroundColor: utils.color('semantic.background.secondary'),
      },
    }),
    
    // 尺寸样式
    ...(size === 'sm' && {
      padding: `${utils.spacing(1)} ${utils.spacing(3)}`,
      fontSize: utils.fontSize('xs'),
    }),
    
    ...(size === 'lg' && {
      padding: `${utils.spacing(3)} ${utils.spacing(6)}`,
      fontSize: utils.fontSize('base'),
    }),
  }
  
  return (
    <button style={buttonStyles} {...props}>
      {children}
    </button>
  )
}

// 令牌化卡片组件
function TokenCard({ title, subtitle, children, elevated = false }) {
  const { utils } = useTheme()
  
  const cardStyles = {
    backgroundColor: utils.color('semantic.background.primary'),
    border: `1px solid ${utils.color('semantic.border.primary')}`,
    borderRadius: utils.borderRadius('lg'),
    padding: utils.spacing(6),
    boxShadow: elevated ? utils.shadow('md') : utils.shadow('sm'),
    transition: utils.transition('base'),
    
    '&:hover': elevated ? {
      boxShadow: utils.shadow('lg'),
      transform: 'translateY(-2px)',
    } : {},
  }
  
  return (
    <div style={cardStyles}>
      {title && (
        <h3 style={{
          margin: 0,
          marginBottom: utils.spacing(2),
          fontSize: utils.fontSize('lg'),
          fontWeight: typographyTokens.fontWeights.semibold,
          color: utils.color('semantic.text.primary'),
        }}>
          {title}
        </h3>
      )}
      
      {subtitle && (
        <p style={{
          margin: 0,
          marginBottom: utils.spacing(4),
          fontSize: utils.fontSize('sm'),
          color: utils.color('semantic.text.secondary'),
        }}>
          {subtitle}
        </p>
      )}
      
      <div style={{
        fontSize: utils.fontSize('base'),
        lineHeight: typographyTokens.lineHeights.normal,
        color: utils.color('semantic.text.primary'),
      }}>
        {children}
      </div>
    </div>
  )
}

通过组件库和设计系统的合理选择和定制,可以构建出视觉一致、开发高效、用户体验优秀的产品。设计令牌系统为整个产品提供了统一的设计语言,确保了不同平台和产品间的一致性。


7.6 响应式设计

7.6.1 断点系统设计

响应式断点配置:

// breakpoints.js - 断点定义
export const breakpoints = {
  xs: '480px',    // 手机竖屏
  sm: '768px',    // 平板竖屏/手机横屏
  md: '992px',    // 平板横屏/小桌面
  lg: '1200px',   // 桌面
  xl: '1600px',   // 大桌面
  xxl: '1920px',  // 超大桌面
}

// 媒体查询工具
export const mediaQueries = {
  xs: `@media (max-width: ${breakpoints.xs})`,
  sm: `@media (min-width: ${breakpoints.xs}) and (max-width: ${breakpoints.sm})`,
  md: `@media (min-width: ${breakpoints.sm}) and (max-width: ${breakpoints.md})`,
  lg: `@media (min-width: ${breakpoints.md}) and (max-width: ${breakpoints.lg})`,
  xl: `@media (min-width: ${breakpoints.lg}) and (max-width: ${breakpoints.xl})`,
  xxl: `@media (min-width: ${breakpoints.xl})`,
  
  // 最小宽度断点
  minXs: `@media (min-width: ${breakpoints.xs})`,
  minSm: `@media (min-width: ${breakpoints.sm})`,
  minMd: `@media (min-width: ${breakpoints.md})`,
  minLg: `@media (min-width: ${breakpoints.lg})`,
  minXl: `@media (min-width: ${breakpoints.xl})`,
  
  // 最大宽度断点
  maxXs: `@media (max-width: ${breakpoints.xs})`,
  maxSm: `@media (max-width: ${breakpoints.sm})`,
  maxMd: `@media (max-width: ${breakpoints.md})`,
  maxLg: `@media (max-width: ${breakpoints.lg})`,
  maxXl: `@media (max-width: ${breakpoints.xl})`,
}

7.6.2 CSS Grid与Flexbox响应式

响应式布局实现:

/* 响应式Grid系统 */
.responsive-grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr; /* 移动端默认单列 */
  
  /* 小屏幕:2列 */
  @media (min-width: 768px) {
    grid-template-columns: repeat(2, 1fr);
  }
  
  /* 中等屏幕:3列 */
  @media (min-width: 992px) {
    grid-template-columns: repeat(3, 1fr);
  }
  
  /* 大屏幕:4列 */
  @media (min-width: 1200px) {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* 弹性网格布局 */
.auto-grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

/* 响应式Flexbox */
.flex-container {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  
  @media (min-width: 768px) {
    flex-direction: row;
    flex-wrap: wrap;
  }
}

.flex-item {
  flex: 1;
  min-width: 0; /* 防止flex项目溢出 */
  
  @media (min-width: 768px) {
    flex: 0 0 calc(50% - 0.5rem); /* 两列布局 */
  }
  
  @media (min-width: 992px) {
    flex: 0 0 calc(33.333% - 0.666rem); /* 三列布局 */
  }
}

7.7 主题系统

7.7.1 主题架构设计

多主题支持:

// themes/index.js - 主题配置
export const lightTheme = {
  colors: {
    primary: '#007bff',
    background: '#ffffff',
    surface: '#f8f9fa',
    text: '#212529',
    textSecondary: '#6c757d',
  },
  mode: 'light',
}

export const darkTheme = {
  colors: {
    primary: '#4dabf7',
    background: '#1a1a1a',
    surface: '#2d2d2d',
    text: '#ffffff',
    textSecondary: '#a0a0a0',
  },
  mode: 'dark',
}

// 主题上下文
const ThemeContext = createContext()

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState(lightTheme)
  
  const toggleTheme = () => {
    setTheme(prev => prev.mode === 'light' ? darkTheme : lightTheme)
  }
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

7.7.2 CSS变量主题切换

动态主题实现:

// hooks/useTheme.js
export function useTheme() {
  const { theme, setTheme } = useContext(ThemeContext)
  
  const applyTheme = useCallback((themeConfig) => {
    const root = document.documentElement
    
    Object.entries(themeConfig.colors).forEach(([key, value]) => {
      root.style.setProperty(`--color-${key}`, value)
    })
    
    setTheme(themeConfig)
  }, [setTheme])
  
  return { theme, setTheme: applyTheme }
}

7.8 样式优化和最佳实践

7.8.1 性能优化策略

CSS性能优化:

// 1. 关键CSS内联
function CriticalCSS() {
  return (
    <style>{`
      .loading-spinner {
        width: 40px;
        height: 40px;
        border: 4px solid #f3f3f3;
        border-top: 4px solid #007bff;
        border-radius: 50%;
        animation: spin 1s linear infinite;
      }
      @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
    `}</style>
  )
}

// 2. 样式懒加载
function LazyStyles() {
  useEffect(() => {
    const loadStyles = async () => {
      const styles = await import('./heavy-components.css')
      // 动态插入样式
    }
    
    // 在用户交互时加载
    document.addEventListener('mouseover', loadStyles, { once: true })
  }, [])
}

7.8.2 代码组织最佳实践

样式文件结构:

src/
├── styles/
│   ├── tokens/          # 设计令牌
│   │   ├── colors.js
│   │   ├── spacing.js
│   │   └── typography.js
│   ├── components/      # 组件样式
│   │   ├── Button.module.css
│   │   ├── Card.module.css
│   │   └── Form.module.css
│   ├── utilities/      # 工具类
│   │   ├── layout.css
│   │   └── spacing.css
│   └── themes/         # 主题样式
│       ├── light.css
│       └── dark.css

7.8.3 可维护性实践

样式命名规范:

// BEM命名法示例
const cardStyles = {
  // Block
  card: 'card',
  
  // Element
  cardHeader: 'card__header',
  cardTitle: 'card__title',
  cardContent: 'card__content',
  cardFooter: 'card__footer',
  
  // Modifier
  cardElevated: 'card--elevated',
  cardCompact: 'card--compact',
  cardLoading: 'card--loading',
}

// 使用示例
function Card({ elevated, compact, children }) {
  const className = [
    cardStyles.card,
    elevated && cardStyles.cardElevated,
    compact && cardStyles.cardCompact,
  ].filter(Boolean).join(' ')
  
  return <div className={className}>{children}</div>
}

🎉 第7章 学习总结

✅ 完成任务清单

恭喜!你已经成功完成了第7章"样式处理详解"的全部学习任务:

  • CSS基础与内联样式 - 掌握React中的样式应用方式和特殊性规则
  • CSS模块化作用域 - 熟练使用CSS Modules实现样式隔离
  • CSS-in-JS解决方案 - 掌握styled-components和Emotion的使用
  • 预处理器集成 - 学会Sass、Less和PostCSS的配置和优化
  • 组件库和设计系统 - 能够选择和定制主流UI组件库
  • 响应式设计 - 实现多设备适配的响应式布局
  • 主题系统 - 构建灵活的主题切换机制
  • 样式优化和最佳实践 - 掌握性能优化和代码组织规范

📚 核心知识点回顾

🎨 样式方案对比

  • 内联样式:动态性最强,适合条件渲染
  • CSS Modules:自动作用域隔离,适合组件化开发
  • CSS-in-JS:组件化样式,强大的主题系统
  • 预处理器:增强CSS功能,提升开发效率

🔧 核心技术能力

  • 样式架构设计:构建可维护的样式系统
  • 性能优化:实施样式加载和渲染优化
  • 主题定制:实现灵活的主题切换机制
  • 响应式实现:适配不同设备和屏幕尺寸

🛠️ 实用工具和函数

  • 设计令牌系统:统一的设计语言
  • 响应式工具:断点管理和媒体查询
  • 样式工具函数:提升开发效率的辅助函数

🚀 下一步学习建议

  1. 实践项目:构建完整的响应式Web应用
  2. 深入研究:探索CSS-in-JS库的源码实现
  3. 性能监控:建立样式性能监控体系
  4. 设计系统:构建企业级设计系统
  5. 跨平台:学习跨平台样式解决方案

💡 核心心得

  • 选择合适的方案:根据项目需求选择最适合的样式方案
  • 性能优先:始终关注样式对应用性能的影响
  • 可维护性:良好的代码组织是长期维护的关键
  • 用户体验:样式最终服务于用户体验
  • 持续学习:CSS技术在不断发展,需要持续学习新特性

通过本章学习,你已经掌握了React应用中样式处理的方方面面,可以构建出美观、高性能、可维护的现代化Web应用!🎉


第8章 高级特性

🚀 章节学习目标

本章将深入探讨React的高级特性和进阶技术,包括高阶组件、自定义Hooks、Context API、错误边界、性能优化等核心技术。通过学习本章,你将掌握构建复杂、高性能React应用的必备技能,提升代码的可维护性和可扩展性。

📋 学习任务清单

✅ HOC(高阶组件)详解

✅ Render Props模式

✅ 自定义Hooks详解

✅ Context API高级用法

✅ 错误边界和错误处理

✅ 性能优化技巧

✅ 组件设计模式

✅ TypeScript与React最佳实践

🎯 核心知识点概览

  1. 组件复用模式 - HOC、Render Props、自定义Hooks的对比和应用
  2. 状态管理进阶 - Context API的深度使用和性能优化
  3. 错误处理机制 - 构建健壮的错误处理和恢复体系
  4. 性能优化策略 - 从组件到应用的全链路性能优化
  5. 设计模式应用 - 在React中应用经典软件设计模式
  6. 类型安全保障 - TypeScript与React的深度集成
  7. 架构设计原则 - 构建可扩展、可维护的应用架构
  8. 最佳实践总结 - 业界成熟的高级开发实践

💡 学习建议

  • 理论结合实践:每个知识点都要通过实际项目来验证和掌握
  • 性能优先思维:在开发过程中始终考虑性能影响
  • 设计模式学习:理解背后的设计思想和适用场景
  • 代码质量:注重代码的可读性、可维护性和可测试性
  • 持续学习:关注React生态的最新发展和最佳实践

posted @ 2025-11-29 18:20  seven3306  阅读(4)  评论(0)    收藏  举报