痛点:刷新闪烁
最朴素的暗色模式实现,是在按钮里直接 document.documentElement.classList.toggle('dark')。它能用,但有两个问题:
- 刷新闪烁(FOUC):页面加载时先显示浅色,再被脚本切回暗色。
- 状态分散:多个组件各管各的,状态不同步。
解决:组合式 API + 持久化
把逻辑抽成一个 useTheme 组合式函数,借助 Nuxt 的 useState 在全站共享同一份响应式状态:
export const useTheme = () => {
const theme = useState<'light' | 'dark'>('theme', () => 'light')
const toggle = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
applyTheme()
persist()
}
// 初始化时从 localStorage 读取,并立即写入 <html> class
const initTheme = () => { /* ... */ }
return { theme, toggle, initTheme }
}
配合 CSS 变量
JS 只负责切换一个 dark 类名,所有颜色变化交给 CSS 变量完成。这样切换瞬间完成、零重排开销:
:root { --c-bg: #fbfbfa; }
html.dark { --c-bg: #0f1115; }
body { background: var(--c-bg); }
避免 FOUC 的关键
在 app.vue 的 setup 阶段(最早执行的脚本)就调用 initTheme(),把正确的类名写到 <html> 上——在浏览器绘制第一帧之前就完成主题应用,用户便感知不到闪烁。
这一套组合拳,正是 Vue 3 "逻辑复用"理念的最小而美示例。