Next.js使用recoil-persist持久化,首次加载darkMode对应className不生效
问题根因
recoil-persist默认采用异步方式从localStorage读取持久化状态,页面首次渲染时,darkModeAtom返回的是你定义的初始默认值(通常为false),等本地存储读取完成后才会更新为持久化的true值。- 如果你使用了SSR/SSG类框架(比如Next.js),服务端渲染阶段无法访问浏览器环境的localStorage,输出的HTML结构里外层div天生不携带
dark类。客户端hydrate阶段为了避免DOM不匹配报错,第一次渲染会完全对齐服务端输出的结构,不会直接应用本地存储的状态值。 - 你在控制台看到的
darkMode: true实际是异步读取完成后第二次渲染的输出,styled-jsx的全局样式是客户端动态注入的,不受服务端输出约束,所以状态更新后会立刻生效,而DOM的className更新被hydrate逻辑限制,才会出现状态正确但类名不生效的问题。
解决方案
方案1:同步初始化atom默认值
定义darkModeAtom时直接从localStorage同步读取值作为默认值,避免异步更新延迟:
import { atom } from 'recoil' import { persistAtom } from 'recoil-persist' const darkModeAtom = atom<boolean>({ key: 'darkModeAtom', // 非浏览器环境返回默认值false,浏览器环境直接读本地存储 default: typeof window !== 'undefined' ? JSON.parse(localStorage.getItem('darkMode') || 'false') : false, effects_UNSTABLE: [persistAtom], })
方案2:增加挂载状态判断(适配SSR场景)
在Layout组件内等待组件挂载完成后再应用dark类,保证此时状态已经初始化完成:
import { useState, useEffect } from 'react' const Layout: React.FC<LayoutProps> = ({ children }) => { const darkMode = useRecoilValue(darkModeAtom) const [isMounted, setIsMounted] = useState(false) useEffect(() => { setIsMounted(true) }, []) return ( <div className={`max-w-6xl mx-auto my-2 ${isMounted && darkMode ? 'dark' : ''}`}> <Nav /> {children} <style jsx global>{` body { background-color: ${darkMode ? '#12232e' : '#eefbfb'}; } `}</style> </div> ) }
内容的提问来源于stack exchange,提问作者Pranta
相关产品推荐
相关产品推荐

