react19+nextjs+antd切换主题颜色

在 React 19 + Next.js + Ant Design 项目中实现主题切换功能,可以通过以下步骤完成。这里将提供完整方案,包含静态主题切换和动态实时切换两种方式。


一、基础配置(Ant Design 主题支持)

1. 安装必要依赖
bash 复制代码
npm install antd @ant-design/cssinjs
2. 配置 Next.js 支持 CSS-in-JS

修改 next.config.js:

javascript 复制代码
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,
  },
  transpilePackages: ['antd'],
};

module.exports = nextConfig;
3. 创建主题配置文件

src/theme/themeConfig.ts:

typescript 复制代码
import type { ThemeConfig } from 'antd';

// 基础主题配置
export const baseTheme: ThemeConfig = {
  token: {
    colorPrimary: '#1677ff',
    borderRadius: 6,
  },
};

// 暗黑主题
export const darkTheme: ThemeConfig = {
  token: {
    ...baseTheme.token,
    colorBgBase: '#141414',
    colorTextBase: '#f0f0f0',
    colorPrimary: '#177ddc',
  },
  algorithm: 'dark',
};

// 自定义主题
export const customTheme: ThemeConfig = {
  token: {
    ...baseTheme.token,
    colorPrimary: '#ff4d4f',
  },
};

二、静态主题切换方案(SSR 兼容)

1. 创建 ThemeProvider 组件

src/providers/ThemeProvider.tsx:

tsx 复制代码
'use client';

import { ReactNode, useState } from 'react';
import { ConfigProvider, theme } from 'antd';
import { baseTheme, darkTheme, customTheme } from '@/theme/themeConfig';

type ThemeType = 'light' | 'dark' | 'custom';

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [currentTheme, setCurrentTheme] = useState<ThemeType>('light');

  const getThemeConfig = () => {
    switch (currentTheme) {
      case 'dark':
        return darkTheme;
      case 'custom':
        return customTheme;
      default:
        return baseTheme;
    }
  };

  return (
    <ConfigProvider theme={getThemeConfig()}>
      {children}
      {/* 主题切换控件可以放在这里 */}
    </ConfigProvider>
  );
}
2. 在布局文件中使用

src/app/layout.tsx:

tsx 复制代码
import { ThemeProvider } from '@/providers/ThemeProvider';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <ThemeProvider>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

三、动态实时切换方案(含持久化)

1. 扩展 ThemeProvider
tsx 复制代码
'use client';

import { ReactNode, useEffect, useState } from 'react';
import { ConfigProvider, theme } from 'antd';
import { baseTheme, darkTheme, customTheme } from '@/theme/themeConfig';

type ThemeType = 'light' | 'dark' | 'custom';

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [currentTheme, setCurrentTheme] = useState<ThemeType>('light');

  // 初始化时读取本地存储
  useEffect(() => {
    const savedTheme = localStorage.getItem('theme') as ThemeType || 'light';
    setCurrentTheme(savedTheme);
  }, []);

  // 主题变化时保存到本地存储
  useEffect(() => {
    localStorage.setItem('theme', currentTheme);
    document.documentElement.setAttribute('data-theme', currentTheme);
  }, [currentTheme]);

  const getThemeConfig = () => {
    switch (currentTheme) {
      case 'dark':
        return darkTheme;
      case 'custom':
        return customTheme;
      default:
        return baseTheme;
    }
  };

  const toggleTheme = () => {
    setCurrentTheme(prev => {
      if (prev === 'light') return 'dark';
      if (prev === 'dark') return 'custom';
      return 'light';
    });
  };

  return (
    <ConfigProvider theme={getThemeConfig()}>
      {children}
      <button 
        onClick={toggleTheme}
        style={{ position: 'fixed', right: 20, bottom: 20 }}
      >
        切换主题
      </button>
    </ConfigProvider>
  );
}
2. 添加全局 CSS 变量

src/app/globals.css:

css 复制代码
:root {
  --primary-color: #1677ff;
}

[data-theme="dark"] {
  --primary-color: #177ddc;
}

[data-theme="custom"] {
  --primary-color: #ff4d4f;
}

四、高级功能扩展

1. 主题色选择器
tsx 复制代码
import { ColorPicker } from 'antd';

function ThemeColorPicker() {
  const [color, setColor] = useState('#1677ff');

  return (
    <ConfigProvider
      theme={{
        token: {
          colorPrimary: color,
        },
      }}
    >
      <ColorPicker 
        value={color} 
        onChangeComplete={(color) => setColor(color.toHexString())}
      />
    </ConfigProvider>
  );
}
2. 使用 CSS 变量动态主题
tsx 复制代码
// 在 ThemeProvider 中添加
useEffect(() => {
  document.documentElement.style.setProperty('--primary-color', getThemeConfig().token.colorPrimary);
}, [currentTheme]);

五、解决 Next.js 的 SSR 问题

1. 创建 useClientTheme Hook

src/hooks/useClientTheme.ts:

typescript 复制代码
'use client';

import { useEffect, useState } from 'react';

export function useClientTheme() {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  useEffect(() => {
    // 只在客户端执行
    const savedTheme = localStorage.getItem('theme') || 'light';
    setTheme(savedTheme as 'light' | 'dark');
  }, []);

  return theme;
}
2. 修改组件使用方式
tsx 复制代码
'use client';

import { useClientTheme } from '@/hooks/useClientTheme';

export default function ClientComponent() {
  const theme = useClientTheme();
  
  return (
    <div data-theme={theme}>
      {/* 内容 */}
    </div>
  );
}

六、完整实现流程图

User UI ThemeProvider LocalStorage ConfigProvider 点击切换主题按钮 调用toggleTheme() 保存新主题 更新主题配置 重新渲染应用样式 显示新主题界面 User UI ThemeProvider LocalStorage ConfigProvider


七、最佳实践建议

  1. 性能优化:

    • 使用 React.memo 避免不必要的重渲染
    • 将主题状态提升到最顶层组件
  2. TypeScript 强化:

    typescript 复制代码
    type ThemeType = 'light' | 'dark' | 'custom';
    interface ThemeContextType {
      theme: ThemeType;
      setTheme: (theme: ThemeType) => void;
    }
  3. 服务端渲染兼容:

    • 使用 dynamic 导入客户端组件
    • 在 _document.tsx 中初始化主题
  4. 测试方案:

    javascript 复制代码
    // 测试主题切换
    test('should toggle theme correctly', () => {
      render(<ThemeProvider />);
      const button = screen.getByText('切换主题');
      fireEvent.click(button);
      expect(localStorage.getItem('theme')).toBe('dark');
    });

通过以上方案,你可以实现一个完整、高效且可维护的主题切换系统,同时兼容 Next.js 的服务端渲染特性。

相关推荐
Highcharts.js2 分钟前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn26 分钟前
构建工具
前端
FRing031 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
JavaPub-rodert2 小时前
从零搭建通用管理后台:ShiyuAdmin 的 Go、React 与 RBAC 实践
开发语言·react.js·golang
kyriewen3 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒3 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫4 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库
jerrywus4 小时前
我用 GPT-6.1-sol 做了台会讲解的 3D 售货机:60 罐饮料,一口没喝上
前端·agent·claude
孟陬5 小时前
Foobar 技术周刊#159:当看不懂代码时,如何高效排障调试。脏页陷阱:一条只读查询,写入数 TB 数据。推测解码:大模型加速利器
前端·人工智能·后端
Zhao-Jichao6 小时前
AI Prompting for Everyone 课程笔记 - P17 图像生成 Imaging Generation
前端