告别刺眼屏幕:React暗黑模式无缝切换全指南

告别刺眼屏幕:React暗黑模式无缝切换全指南

【免费下载链接】react facebook/react: React 是一个用于构建用户界面的 JavaScript 库,可以用于构建 Web 应用程序和移动应用程序,支持多种平台,如 Web,Android,iOS 等。 【免费下载链接】react 项目地址: https://gitcode.com/GitHub_Trending/re/react

你是否也曾在深夜 Coding 时被纯白背景刺痛双眼?是否想让你的 React 应用自动适应系统主题?本文将带你从零实现一个支持自动切换、用户手动控制的暗黑模式系统,只需三步即可让你的应用拥有专业级主题体验。

主题切换的核心原理

现代浏览器已内置暗色模式支持,通过 prefers-color-scheme 媒体查询可获取系统主题偏好。React 应用实现主题切换的核心在于:

  1. CSS变量(CSS Variables) 定义主题配色方案
  2. Context API 跨组件共享主题状态
  3. localStorage 持久化用户主题偏好

React DevTools 团队在 packages/react-devtools-extensions/popups/shared.css 中采用了基础的媒体查询实现:

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
  }
  
  @supports (-moz-appearance:none) {
    :root { background: black; }
    body { color: white; }
  }
}

但这仅能实现系统主题跟随,无法满足用户手动切换需求。我们需要更完善的实现方案。

第一步:定义主题变量系统

创建 src/styles/theme.css 文件,使用 CSS 变量定义两套主题:

/* 浅色主题变量 */
:root {
  --bg-color: #ffffff;
  --text-color: #333333;
  --card-bg: #f5f5f5;
  --border-color: #e0e0e0;
}

/* 深色主题变量 */
[data-theme="dark"] {
  --bg-color: #1a1a1a;
  --text-color: #e0e0e0;
  --card-bg: #2d2d2d;
  --border-color: #444444;
}

/* 组件样式使用变量 */
body {
  background-color: var(--bg-color);
  color: var(--text-color);
  transition: background-color 0.3s ease;
}

.card {
  background-color: var(--card-bg);
  border: 1px solid var(--border-color);
  border-radius: 8px;
  padding: 16px;
}

这种方式的优势在于:

  • 只需切换 data-theme 属性即可全局更新样式
  • 支持过渡动画,实现平滑主题切换
  • 便于扩展更多主题(如 sepia 棕褐色模式)

第二步:创建主题上下文与Provider

创建 src/contexts/ThemeContext.js,管理主题状态:

import React, { createContext, useState, useEffect } from 'react';

// 创建上下文
export const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  // 初始化主题状态:优先从localStorage读取,否则使用系统主题
  const [theme, setTheme] = useState('light');
  
  // 检测系统主题
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  
  // 初始化主题
  useEffect(() => {
    const savedTheme = localStorage.getItem('theme');
    if (savedTheme) {
      setTheme(savedTheme);
      document.documentElement.setAttribute('data-theme', savedTheme);
    } else if (prefersDark) {
      setTheme('dark');
      document.documentElement.setAttribute('data-theme', 'dark');
    }
  }, []);
  
  // 切换主题
  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    document.documentElement.setAttribute('data-theme', newTheme);
    localStorage.setItem('theme', newTheme); // 持久化保存
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

第三步:实现主题切换组件

创建 src/components/ThemeToggle.jsx

import React, { useContext } from 'react';
import { ThemeContext } from '../contexts/ThemeContext';

export const ThemeToggle = () => {
  const { theme, toggleTheme } = useContext(ThemeContext);
  
  return (
    <button 
      onClick={toggleTheme}
      style={{
        background: 'var(--bg-color)',
        color: 'var(--text-color)',
        border: '1px solid var(--border-color)',
        padding: '8px 16px',
        borderRadius: '4px',
        cursor: 'pointer'
      }}
    >
      当前主题:{theme === 'light' ? '浅色' : '深色'}
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ marginLeft: '8px' }}>
        {theme === 'light' ? (
          <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
        ) : (
          <circle cx="12" cy="12" r="5" />
          <line x1="12" y1="1" x2="12" y2="3" />
          <line x1="12" y1="21" x2="12" y2="23" />
          <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
          <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
          <line x1="1" y1="12" x2="3" y2="12" />
          <line x1="21" y1="12" x2="23" y2="12" />
          <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
          <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
        )}
      </svg>
    </button>
  );
};

高级优化:主题切换动画与性能

为提升用户体验,可添加主题切换过渡动画。参考 fixtures/flight-parcel/src/Todos.css 中的实现:

/* 添加到theme.css */
body {
  transition: background-color 0.3s ease, color 0.3s ease;
}

/* 为卡片等组件添加过渡 */
.card {
  transition: background-color 0.3s ease, border-color 0.3s ease;
}

性能优化建议:

  1. 使用 useMemo 缓存主题计算结果
  2. 避免在主题切换时触发大量重渲染
  3. 对主题相关计算使用 useCallback 记忆函数

完整实现效果

将 ThemeProvider 包装在应用根组件:

import { ThemeProvider } from './contexts/ThemeContext';
import './styles/theme.css';

function App() {
  return (
    <ThemeProvider>
      <div className="App">
        {/* 应用内容 */}
        <header>
          <h1>React主题切换示例</h1>
          <ThemeToggle />
        </header>
        <main>
          <div className="card">
            <h2>主题切换演示</h2>
            <p>当前主题将保存在localStorage中,刷新页面后保持不变</p>
          </div>
        </main>
      </div>
    </ThemeProvider>
  );
}

这种实现方案已被 React 生态中的多个项目采用,如:

通过这种方式,你的 React 应用将拥有专业级的主题切换功能,完美适配用户的系统偏好和使用场景。

【免费下载链接】react facebook/react: React 是一个用于构建用户界面的 JavaScript 库,可以用于构建 Web 应用程序和移动应用程序,支持多种平台,如 Web,Android,iOS 等。 【免费下载链接】react 项目地址: https://gitcode.com/GitHub_Trending/re/react

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值