You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Material-UI实现深色主题时localStorage生产环境读取失效如何解决?

问题根因

该项目基于Gatsby框架开发,生产环境会执行服务端渲染(SSR):服务端运行阶段不存在window对象,因此initialState的theme属性在SSR阶段就被固定为默认值light。而useReducer的初始值仅在组件首次实例化时读取一次,客户端hydrate阶段不会重新计算初始值,即使客户端能正常读取localStorage内的存储值,也无法覆盖已经初始化的state,最终导致生产环境刷新后主题回退为浅色。

修复步骤

  1. 移除初始化阶段读取localStorage的逻辑,避免SSR与客户端hydrate内容不一致导致的报错,初始主题直接设置为默认值light
  2. 新增仅在客户端挂载后执行的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 12:27:05