在 React 19 + Next.js + Ant Design 項目中實現主題切換功能,可以通過以下步驟完成。這里將提供完整方案,包含靜態主題切換和動態實時切換兩種方式。
一、基礎配置(Ant Design 主題支持)
1. 安裝必要依賴
npm install antd @ant-design/cssinjs
2. 配置 Next.js 支持 CSS-in-JS
修改 next.config.js
:
/** @type {import('next').NextConfig} */
const nextConfig = {experimental: {appDir: true,},transpilePackages: ['antd'],
};module.exports = nextConfig;
3. 創建主題配置文件
src/theme/themeConfig.ts
:
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
:
'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
:
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
'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
:
:root {--primary-color: #1677ff;
}[data-theme="dark"] {--primary-color: #177ddc;
}[data-theme="custom"] {--primary-color: #ff4d4f;
}
四、高級功能擴展
1. 主題色選擇器
import { ColorPicker } from 'antd';function ThemeColorPicker() {const [color, setColor] = useState('#1677ff');return (<ConfigProvidertheme={{token: {colorPrimary: color,},}}><ColorPicker value={color} onChangeComplete={(color) => setColor(color.toHexString())}/></ConfigProvider>);
}
2. 使用 CSS 變量動態主題
// 在 ThemeProvider 中添加
useEffect(() => {document.documentElement.style.setProperty('--primary-color', getThemeConfig().token.colorPrimary);
}, [currentTheme]);
五、解決 Next.js 的 SSR 問題
1. 創建 useClientTheme Hook
src/hooks/useClientTheme.ts
:
'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. 修改組件使用方式
'use client';import { useClientTheme } from '@/hooks/useClientTheme';export default function ClientComponent() {const theme = useClientTheme();return (<div data-theme={theme}>{/* 內容 */}</div>);
}
六、完整實現流程圖
七、最佳實踐建議
-
性能優化:
- 使用
React.memo
避免不必要的重渲染 - 將主題狀態提升到最頂層組件
- 使用
-
TypeScript 強化:
type ThemeType = 'light' | 'dark' | 'custom'; interface ThemeContextType {theme: ThemeType;setTheme: (theme: ThemeType) => void; }
-
服務端渲染兼容:
- 使用
dynamic
導入客戶端組件 - 在
_document.tsx
中初始化主題
- 使用
-
測試方案:
// 測試主題切換 test('should toggle theme correctly', () => {render(<ThemeProvider />);const button = screen.getByText('切換主題');fireEvent.click(button);expect(localStorage.getItem('theme')).toBe('dark'); });
通過以上方案,你可以實現一個完整、高效且可維護的主題切換系統,同時兼容 Next.js 的服務端渲染特性。