在 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
七、最佳实践建议
-
性能优化:
- 使用
React.memo
避免不必要的重渲染 - 将主题状态提升到最顶层组件
- 使用
-
TypeScript 强化:
typescripttype ThemeType = 'light' | 'dark' | 'custom'; interface ThemeContextType { theme: ThemeType; setTheme: (theme: ThemeType) => void; }
-
服务端渲染兼容:
- 使用
dynamic
导入客户端组件 - 在
_document.tsx
中初始化主题
- 使用
-
测试方案:
javascript// 测试主题切换 test('should toggle theme correctly', () => { render(<ThemeProvider />); const button = screen.getByText('切换主题'); fireEvent.click(button); expect(localStorage.getItem('theme')).toBe('dark'); });
通过以上方案,你可以实现一个完整、高效且可维护的主题切换系统,同时兼容 Next.js 的服务端渲染特性。