Skip to content

整个颜色生效的流程可以简化为: Chat 组件中使用了 bg-theme 类名 浏览器查找 Tailwind 生成的 CSS 规则:.bg-theme { background-color: var(--bg-color); } 浏览器解析 CSS 变量 --bg-color 的值,查询 themes.css 文件 根据当前 HTML 根元素的 data-theme 属性,确定使用哪个选择器中的变量值 应用找到的颜色值到页面元素上 这就是为什么更改 data-theme 属性后,页面颜色会立即变化 - 因为这改变了 CSS 变量的值,而不需要更改类名。 简化流程图 Chat组件 Tailwind CSS Themes.css 浏览器渲染 ┌──────────┐ ┌──────────────┐ ┌───────────────┐ ┌────────────┐ │bg-theme类│──查找──▶│background-color:│──查找──▶│--bg-color: #f9fafb│──应用──▶│最终背景颜色 │ └──────────┘ │var(--bg-color)│ │ (亮色主题) │ └────────────┘ └──────────────┘ │ │ │--bg-color: #111827│ │ (暗色主题) │ └───────────────┘ // ... existing code ...

graph TD
    subgraph "1. 定义阶段"
        A1["themes.css - 定义 CSS 变量"] --> |"定义主题变量"| A2["CSS 变量集合:
--primary-color
--text-color
--bg-color
--card-bg
等"] A3["tailwind.config.js - 自定义类映射"] --> |"映射类名到变量"| A4["自定义类定义:
bg-theme: var(--bg-color)
text-theme: var(--text-color)
等"] end subgraph "2. 构建阶段" B1["Tailwind 编译过程"] --> |"扫描项目文件"| B2["识别使用的类名"] B2 --> |"生成CSS规则"| B3["最终CSS输出:
.bg-theme { background-color: var(--bg-color) }
.text-theme { color: var(--text-color) }
等"] end subgraph "3. 初始化阶段" C1["应用启动"] --> |"导入样式"| C2["加载 themes.css"] C1 --> |"调用主题服务"| C3["themeService.getCurrentTheme()"] C3 --> |"检查存储"| C4["localStorage.getItem('app_theme')"] C4 --> |"有保存的主题"| C5["应用保存的主题"] C4 --> |"没有保存的主题"| C6["应用默认主题"] C5 --> C7["设置 data-theme 属性"] C6 --> C7 C7 --> |"触发选择器匹配"| C8["激活对应主题的 CSS 变量"] end subgraph "4. 渲染阶段" D1["React 渲染 Chat 组件"] --> |"应用类名"| D2["DOM 元素:
div class=bg-theme
button class=bg-primary"] D2 --> |"浏览器解析"| D3["查找匹配的 CSS 规则"] D3 --> |"处理 var() 函数"| D4["查找 CSS 变量值"] D4 --> |"根据 data-theme 属性"| D5["选择正确的变量定义"] D5 --> |"应用最终值"| D6["计算样式:
background-color: #f9fafb
color: #1f2937"] end subgraph "5. 用户交互阶段" E1["用户点击主题切换按钮"] --> |"触发事件处理"| E2["onClick处理函数"] E2 --> |"调用主题服务"| E3["themeService.setTheme('dark')"] E3 --> |"更新内部状态"| E4["currentTheme = 'dark'"] E3 --> |"更新DOM属性"| E5["document根元素设置data-theme='dark'"] E3 --> |"保存偏好"| E6["localStorage存储主题"] E3 --> |"触发事件"| E7["dispatchEvent('themeChanged')"] E5 --> |"触发选择器匹配"| E8["匹配暗色主题CSS变量"] E8 --> |"CSS变量值更新"| E9["--bg-color更新为暗色值"] end subgraph "6. 样式重新计算阶段" F1["CSS 变量值改变"] --> |"自动重新计算"| F2["使用变量的所有规则更新"] F2 --> |"应用过渡效果"| F3["颜色过渡动画"] F3 --> |"页面重绘"| F4["显示新主题颜色"] end subgraph "7. 组件穿透机制" G1["全局 CSS 变量"] --> |"继承机制"| G2["子元素访问根元素变量"] G2 --> |"应用到第三方组件"| G3["第三方组件颜色覆盖"] G1 --> |"Tailwind工具类"| G4["自定义工具类使用"] G4 --> |"组件库适配"| G5["UI组件使用主题变量"] end %% 连接各个阶段 A4 --> B1 B3 --> D3 C8 --> D5 E9 --> F1 F4 --> |"用户继续交互"| E1 G5 --> |"应用到所有组件"| D1 %% 单个样式的生效路径 subgraph "8. 单个样式生效流程示例" H1["Chat组件使用bg-theme类"] --> |"应用到DOM"| H2["div元素带bg-theme类"] H2 --> |"CSS规则匹配"| H3["匹配bg-theme规则"] H3 --> |"变量解析"| H4["查找--bg-color变量"] H4 --> |"根据data-theme"| H5["获取对应主题颜色值"] H5 --> |"最终样式"| H6["应用背景颜色"] end %% 大量样式的处理 subgraph "9. 大量样式处理机制" I1["多组件使用主题类"] --> |"统一类名"| I2["共享CSS变量引用"] I2 --> |"变量值改变"| I3["一次更改全局生效"] I3 --> |"批量处理"| I4["浏览器优化渲染"] I4 --> |"性能优势"| I5["优于直接类名更改"] end %% 第三方组件穿透 subgraph "10. 第三方组件穿透示例" J1["外部UI组件"] --> |"类名适配"| J2["添加自定义主题类"] J2 --> |"主题继承"| J3["使用主题变量"] J1 --> |"样式覆盖"| J4["全局选择器覆盖"] J4 --> |"变量应用"| J5["组件使用主题色"] end

穿透流程:

graph TD
    A["themeService.setTheme('dark')"] --> B1["设置 data-theme 属性"]
    A --> B2["设置 body 类名"]
    
    B1 --> C1["CSS 变量值更新:
--bg-color: #111827"] B2 --> C2["CSS 类选择器匹配:
.theme-dark .component"] C1 --> D1["使用 CSS 变量的元素更新:
var(--bg-color)"] C2 --> D2["第三方组件样式被覆盖"] D1 --> E["自定义组件以新主题显示"] D2 --> F["第三方组件以新主题显示"]

1. 使用 CSS 变量定义主题颜色:

css
/* theme.css 文件 */
:root {
  /* 默认亮色主题 */
  --primary-color: #6366f1;
  --primary-light: #818cf8;
  --primary-dark: #4f46e5;
  --text-color: #1f2937;
  --bg-color: #f9fafb;
  --card-bg: #ffffff;
  --border-color: rgba(99, 102, 241, 0.1);
  --shadow-color: rgba(0, 0, 0, 0.1);
}

/* 暗色主题 覆盖默认主题 */
[data-theme="dark"] {
  --primary-color: #818cf8;
  --primary-light: #a5b4fc;
  --primary-dark: #6366f1;
  --text-color: #f9fafb;
  --bg-color: #111827;
  --card-bg: #1f2937;
  --border-color: rgba(129, 140, 248, 0.2);
  --shadow-color: rgba(0, 0, 0, 0.5);
}

/* 粉色主题 */
[data-theme="pink"] {
  --primary-color: #ec4899;
  --primary-light: #f472b6;
  --primary-dark: #db2777;
  --text-color: #1f2937;
  --bg-color: #fdf2f8;
  --card-bg: #ffffff;
  --border-color: rgba(236, 72, 153, 0.1);
  --shadow-color: rgba(236, 72, 153, 0.1);
}

/* 蓝色主题 */
[data-theme="blue"] {
  --primary-color: #3b82f6;
  --primary-light: #60a5fa;
  --primary-dark: #2563eb;
  --text-color: #1f2937;
  --bg-color: #eff6ff;
  --card-bg: #ffffff;
  --border-color: rgba(59, 130, 246, 0.1);
  --shadow-color: rgba(59, 130, 246, 0.1);
}

2. 创建主题切换管理Service

typeScript
// themeService.ts 文件
// 定义可用的主题类型
export type ThemeType = 'light' | 'dark' | 'pink' | 'blue';

// 主题服务类
class ThemeService {
  private static instance: ThemeService;
  private currentTheme: ThemeType = 'light';
  private readonly STORAGE_KEY = 'app_theme';
  
  // 单例模式
  public static getInstance(): ThemeService {
    if (!ThemeService.instance) {
      ThemeService.instance = new ThemeService();
    }
    return ThemeService.instance;
  }
  
  // 私有构造函数
  private constructor() {
    this.loadThemeFromStorage();
  }
  
  // 从本地存储加载主题
  private loadThemeFromStorage(): void {
    try {
      const savedTheme = localStorage.getItem(this.STORAGE_KEY) as ThemeType;
      if (savedTheme) {
        this.setTheme(savedTheme); // 默认情况下,保存到本地存储
      } else {
        // 如果没有保存的主题,检查系统偏好
        this.checkSystemPreference();
      }
    } catch (error) {
      console.error('加载主题失败:', error);
    }
  }
  
  // 检查系统颜色偏好
  private checkSystemPreference(): void {
    // 检查系统颜色偏好,使用 matchMedia API 判断用户的颜色模式偏好
    // matchMedia 是一个用于检测媒体查询的 API,允许我们根据不同的条件(如屏幕尺寸、方向或颜色模式)来应用不同的样式。
    // matches 属性返回一个布尔值,指示当前文档是否匹配指定的媒体查询。
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
        // 通过 matchMedia API,浏览器可以检测用户的系统主题偏好设置
      this.setTheme('dark', false);
    }
  }
  
  // 设置主题
  public setTheme(theme: ThemeType, saveToStorage: boolean = true): void {
    this.currentTheme = theme;
    // 设置根元素的 data-theme 属性为当前主题,以便在 CSS 中使用该属性进行样式调整
    // setAttribute 方法用于设置指定元素的属性及其值,这里将根元素的 data-theme 属性设置为当前主题,以便在 CSS 中使用该属性进行样式调整。
    document.documentElement.setAttribute('data-theme', theme);
    
    // 添加对应的类名到 body,用于组件主题穿透
    document.body.className = '';
    document.body.classList.add(`theme-${theme}`);
    
    // 保存到本地存储
    if (saveToStorage) {
      localStorage.setItem(this.STORAGE_KEY, theme);
    }
    
    // 触发自定义事件,通知应用主题已更改
    // 创建一个自定义事件对象,并将其 detail 属性设置为当前主题
    const event = new CustomEvent('themeChanged', { detail: { theme } });
    // 触发自定义事件,通知应用主题已更改
    document.dispatchEvent(event);
  }
  
  // 获取当前主题
  public getCurrentTheme(): ThemeType {
    return this.currentTheme;
  }
  
  // 切换到下一个主题
  public toggleTheme(): void {
    const themes: ThemeType[] = ['light', 'dark', 'pink', 'blue'];
    const currentIndex = themes.indexOf(this.currentTheme);
    const nextIndex = (currentIndex + 1) % themes.length;
    this.setTheme(themes[nextIndex]);
  }
}

export default ThemeService.getInstance();

3. 创建主题切换组件

tsx
import React, { useState, useEffect } from 'react';
import themeService, { ThemeType } from '../services/themeService';

const ThemeSwitcher: React.FC = () => {
  const [currentTheme, setCurrentTheme] = useState<ThemeType>(themeService.getCurrentTheme());
  
  useEffect(() => {
    // 监听主题变化事件
    // CustomEvent 是一种用于创建自定义事件的接口,可以携带额外的数据。
    // 它不是 React 自带的,而是原生 JavaScript 的一部分。
    const handleThemeChange = (e: CustomEvent) => {
      setCurrentTheme(e.detail.theme);
    };
    
    document.addEventListener('themeChanged', handleThemeChange as EventListener);
    
    return () => {
      document.removeEventListener('themeChanged', handleThemeChange as EventListener);
    };
  }, []);
  
  const themes: { type: ThemeType; name: string; icon: string }[] = [
    { type: 'light', name: '浅色', icon: '☀️' },
    { type: 'dark', name: '深色', icon: '🌙' },
    { type: 'pink', name: '粉色', icon: '🌸' },
    { type: 'blue', name: '蓝色', icon: '🌊' }
  ];
  
  return (
    <div className="theme-switcher">
      <div className="flex flex-col items-center space-y-2">
        {themes.map((theme) => (
          <button
            key={theme.type}
            onClick={() => themeService.setTheme(theme.type)}
            className={`w-8 h-8 rounded-full flex items-center justify-center transition-all duration-200
                      ${currentTheme === theme.type 
                        ? 'bg-primary text-white shadow-theme transform scale-110' 
                        : 'bg-card text-theme hover:bg-primary/10'}`}
            title={theme.name}
          >
            {theme.icon}
          </button>
        ))}
      </div>
    </div>
  );
};

export default ThemeSwitcher;

4. 在应用中使用主题变量

css
/* index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@import './styles/themes.css';

/* 使用主题变量的全局样式 */
body {
  background-color: var(--bg-color);
  color: var(--text-color);
  transition: background-color 0.3s ease, color 0.3s ease;
}

/* 卡片样式 */
.card {
  background-color: var(--card-bg);
  border: 1px solid var(--border-color);
  box-shadow: 0 4px 6px var(--shadow-color);
}

/* 按钮样式 */
.btn-primary {
  background-color: var(--primary-color);
  color: white;
}

.btn-primary:hover {
  background-color: var(--primary-dark);
}

/* 输入框样式 */
.input {
  background-color: var(--card-bg);
  border: 1px solid var(--border-color);
  color: var(--text-color);
}

/* 主题过渡效果 */
* {
  transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}

/* 动画 */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes scaleIn {
  from { transform: scale(0.95); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

.animate-fadeIn {
  animation: fadeIn 0.3s ease forwards;
}

.animate-scaleIn {
  animation: scaleIn 0.3s ease forwards;
}

5. 在聊天页面中集成主题切换器

tsx
// Chat.tsx
import ThemeSwitcher from '../../components/ThemeSwitcher';

  {/* 主题切换器 */}
  <div className="mt-6 mb-4">
      <ThemeSwitcher />
  </div>

6. 初始化主题

tsx
// index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css'; // 引入 Tailwind CSS,这里引入的是全局样式 
import Chat from './pages/chat/chat'; // 引入 Chat 组件
import themeService from './services/themeService'; // 引入主题服务

// 确保主题服务在应用启动时初始化
themeService.getCurrentTheme(); // 这会触发从本地存储加载主题

// 使用现代的 createRoot API
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
  <React.StrictMode>
    <Chat /> {/* 直接渲染 Chat 组件 */}
  </React.StrictMode>
);

7. 组件主题穿透

javaScript
// tailwind.config.js
module.exports = {
  content: [
    './src/pages/**/*.tsx',
    './src/components/**/*.tsx',
    './src/layouts/**/*.tsx',
  ],
  theme: {
    extend: {
      colors: {
        primary: 'var(--primary-color)',
        'primary-light': 'var(--primary-light)',
        'primary-dark': 'var(--primary-dark)',
        'theme-text': 'var(--text-color)',
        'theme-bg': 'var(--bg-color)',
        'theme-card': 'var(--card-bg)',
        'theme-border': 'var(--border-color)',
      },
      backgroundColor: {
        theme: 'var(--bg-color)',
        card: 'var(--card-bg)',
      },
      textColor: {
        theme: 'var(--text-color)',
      },
      borderColor: {
        theme: 'var(--border-color)',
      },
      boxShadow: {
        theme: '0 4px 6px var(--shadow-color)',
      },
    },
  },
  plugins: [],
}

Q1: bg-theme、text-theme 等类在 Chat 页面中使用,但其他文件中并没有进行定义,为什么?

A1:通过 Tailwind CSS 的配置系统动态生成的。在 tailwind.config.js 文件中扩展了 Tailwind 的主题配置,自定义类如下:

js
// tailwind.config.js
module.exports = {
  // 内容扫描配置...
  theme: {
    extend: {
      backgroundColor: {
        theme: 'var(--bg-color)',    // 这定义了 bg-theme 类
        card: 'var(--card-bg)',      // 这定义了 bg-card 类
      },
      textColor: {
        theme: 'var(--text-color)',  // 这定义了 text-theme 类
      },
      borderColor: {
        theme: 'var(--border-color)', // 这定义了 border-theme 类
      },
      boxShadow: {
        theme: '0 4px 6px var(--shadow-color)', // 这定义了 shadow-theme 类
      },
    },
  },
}

Tailwind Just-In-Time 编译:项目构件时,他会扫描项目中所有使用了这些类的文件,根据 tailwind.config.js eg1(构建中生成的代码):

css
/* 这些规则不存在于您的源码中,而是由 Tailwind 生成 */
.bg-theme { background-color: var(--bg-color); }
.bg-card { background-color: var(--card-bg); }
.text-theme { color: var(--text-color); }
.border-theme { border-color: var(--border-color); }
.shadow-theme { box-shadow: 0 4px 6px var(--shadow-color); }
.bg-primary { background-color: var(--primary-color); }
/* 其他生成的规则... */
  • 构建时生成:它们是在构建过程中动态生成的
  • 配置驱动:定义存在于配置文件中,不是手写的 CSS
  • 按需生成:Tailwind 只生成您实际使用的类,而不是所有可能的类