React+Material-UI实现深色主题时localStorage生产环境读取失效如何解决?
问题根因
该项目基于Gatsby框架开发,生产环境会执行服务端渲染(SSR):服务端运行阶段不存在window对象,因此initialState的theme属性在SSR阶段就被固定为默认值light。而useReducer的初始值仅在组件首次实例化时读取一次,客户端hydrate阶段不会重新计算初始值,即使客户端能正常读取localStorage内的存储值,也无法覆盖已经初始化的state,最终导致生产环境刷新后主题回退为浅色。
修复步骤
- 移除初始化阶段读取localStorage的逻辑,避免SSR与客户端hydrate内容不一致导致的报错,初始主题直接设置为默认值
light - 新增仅在客户端挂载后执行的
useEffect,读取localStorage内的主题配置同步到状态中
修改后完整代码
import { ThemeProvider } from "@mui/material"; import React, { createContext, useEffect, useReducer } from "react"; import { darkTheme, lightTheme } from "../theme/theme"; import { stateProps, themeActionProps, themeActionTypes } from "../types/reducerTypes"; // 初始化阶段不读取localStorage,避免SSR与客户端不一致 const initialState = { theme: 'light' }; const themeReducer = (state: stateProps, action: themeActionProps) => { switch (action.type) { case themeActionTypes.CHANGE_THEME: return { ...state, theme: action.payload } default: return state; } } export const GlobalStateContext = createContext(initialState); export const GlobalDispatchContext = createContext<React.Dispatch<themeActionProps>>(() => { }); const ThemesProvider = ({ children }) => { const [state, dispatch] = useReducer(themeReducer, initialState); // 也可以直接使用state.theme,无需单独维护theme状态,这里保留原逻辑兼容旧代码 const [theme, setTheme] = React.useState('light'); // 状态变更同步本地theme useEffect(() => { setTheme(state.theme); },[state.theme]); // 客户端挂载后读取localStorage同步主题 useEffect(() => { const savedTheme = localStorage.getItem('preferred-theme'); if (savedTheme) { dispatch({ type: themeActionTypes.CHANGE_THEME, payload: savedTheme }) } }, [dispatch]) return ( <GlobalStateContext.Provider value={state}> <GlobalDispatchContext.Provider value={dispatch}> <ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}> {children} </ThemeProvider> </GlobalDispatchContext.Provider> </GlobalStateContext.Provider> ); } export default ThemesProvider;
优化建议
可以直接复用state.theme作为ThemeProvider的主题参数,去掉单独维护的theme state,减少冗余逻辑:
// 删掉单独的theme state和对应的同步useEffect,直接在返回处使用state.theme <ThemeProvider theme={state.theme === 'light' ? lightTheme : darkTheme}>
内容的提问来源于stack exchange,提问作者ahmed abid
相关产品推荐
相关产品推荐

