118 lines
3.8 KiB
TypeScript
118 lines
3.8 KiB
TypeScript
/**
|
||
* 主题管理Hook
|
||
* @author 阿瑞
|
||
* @description 主题切换和状态管理的自定义Hook,支持SSR和防闪烁优化,性能优化
|
||
* @version 2.1.0
|
||
* @created 2024-12-19
|
||
* @updated 优化性能,简化动画逻辑
|
||
*/
|
||
|
||
import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
|
||
import { useSettingActions, useThemeMode } from '@/store/settingStore';
|
||
import { ThemeMode } from '@/types/enum';
|
||
|
||
// 模块级注释:主题切换上下文类型定义
|
||
export interface ThemeContextType {
|
||
isDark: boolean;
|
||
toggleTheme: () => void;
|
||
currentTheme: ThemeMode;
|
||
mounted: boolean; // 关键代码行注释:添加mounted状态避免SSR不匹配
|
||
isTransitioning: boolean; // 关键代码行注释:添加过渡状态追踪
|
||
}
|
||
|
||
// 模块级注释:创建主题上下文
|
||
const ThemeContext = createContext<ThemeContextType>({
|
||
isDark: false,
|
||
toggleTheme: () => {},
|
||
currentTheme: ThemeMode.Light,
|
||
mounted: false,
|
||
isTransitioning: false,
|
||
});
|
||
|
||
// 模块级注释:主题切换Hook
|
||
export const useTheme = (): ThemeContextType => {
|
||
const context = useContext(ThemeContext);
|
||
if (!context) {
|
||
throw new Error('useTheme must be used within a ThemeProvider');
|
||
}
|
||
return context;
|
||
};
|
||
|
||
// 模块级注释:主题提供者组件
|
||
export function ThemeProvider({ children }: { children: React.ReactNode }): React.ReactElement {
|
||
// 关键代码行注释:使用zustand store管理主题状态
|
||
const themeMode = useThemeMode();
|
||
const { toggleThemeMode } = useSettingActions();
|
||
const isDark = themeMode === ThemeMode.Dark;
|
||
|
||
// 关键代码行注释:添加mounted状态,确保只在客户端应用主题
|
||
const [mounted, setMounted] = useState(false);
|
||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||
|
||
// 关键代码行注释:简化的主题应用函数
|
||
const applyTheme = useCallback((darkMode: boolean): void => {
|
||
if (typeof document !== 'undefined' && mounted) {
|
||
const root = document.documentElement;
|
||
const body = document.body;
|
||
|
||
// 关键代码行注释:清除所有主题相关的类名
|
||
body.classList.remove('light-theme', 'dark-theme', 'light', 'dark');
|
||
root.classList.remove('light-theme', 'dark-theme', 'light', 'dark');
|
||
|
||
// 关键代码行注释:应用新的主题类名
|
||
const themeClass = darkMode ? 'dark' : 'light';
|
||
body.classList.add(themeClass);
|
||
root.classList.add(themeClass);
|
||
|
||
// 关键代码行注释:设置data-theme属性
|
||
body.setAttribute('data-theme', themeClass);
|
||
root.setAttribute('data-theme', themeClass);
|
||
}
|
||
}, [mounted]);
|
||
|
||
// 关键代码行注释:优化的主题切换函数
|
||
const enhancedToggleTheme = useCallback(() => {
|
||
if (!isTransitioning) {
|
||
setIsTransitioning(true);
|
||
|
||
// 关键代码行注释:触发主题切换
|
||
toggleThemeMode();
|
||
|
||
// 关键代码行注释:配合分层过渡策略的完成处理
|
||
setTimeout(() => {
|
||
setIsTransitioning(false);
|
||
}, 400); // 配合CSS主过渡时间
|
||
|
||
// 关键代码行注释:添加轻量触觉反馈(如果支持)
|
||
if ('vibrate' in navigator) {
|
||
navigator.vibrate(30);
|
||
}
|
||
}
|
||
}, [toggleThemeMode, isTransitioning]);
|
||
|
||
// 关键代码行注释:组件挂载时设置mounted状态
|
||
useEffect(() => {
|
||
setMounted(true);
|
||
}, []);
|
||
|
||
// 关键代码行注释:只有在客户端挂载后才应用主题
|
||
useEffect(() => {
|
||
if (mounted) {
|
||
applyTheme(isDark);
|
||
}
|
||
}, [isDark, mounted, applyTheme]);
|
||
|
||
return (
|
||
<ThemeContext.Provider
|
||
value={{
|
||
isDark,
|
||
toggleTheme: enhancedToggleTheme,
|
||
currentTheme: themeMode,
|
||
mounted,
|
||
isTransitioning
|
||
}}
|
||
>
|
||
{children}
|
||
</ThemeContext.Provider>
|
||
);
|
||
}
|