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

如何实现Docusaurus与MaterialUI的明暗模式同步?

Docusaurus与MaterialUI明暗模式同步实现方案

问题背景

作为Docusaurus新手,想要实现Docusaurus明暗开关切换时,MaterialUI主题同步切换的效果。当前采用的方案是:通过swizzle包装Docusaurus的ColorModeToggle组件,利用React Context获取MaterialUI的主题切换函数,并在swizzle后的Root组件中用Context Provider包裹MaterialUI的ThemeProvider。但运行时出现错误:

Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

原代码展示

包装后的ColorModeToggle

import React from "react";
import ColorModeToggle from "@theme-original/ColorModeToggle";
import { useToggleTheme } from "@site/src/components/MuiTheme";

export default function ColorModeToggleWrapper(props) {
  console.log("<ColorModeToggleWrapper> properties = " + JSON.stringify(props));

  // "value" 存储Docusaurus的主题值:"light" 或 "dark"
  const { value } = props;
  const muiToggle = useToggleTheme();

  console.log("Docusaurus theme = " + value);
  console.log("MUI theme dark = " + muiToggle());

  return (
    <>
      <ColorModeToggle {...props} />
    </>
  );
}

React Context(MuiTheme)

import React, { useContext } from "react";
import { createTheme, ThemeProvider } from "@mui/material/styles";

const CustomThemeContext = React.createContext({ toggleTheme: () => {} });

const darkTheme = createTheme({
  components: {
    MuiListItemText: {
      styleOverrides: {
        primary: {
          color: "orange",
        },
        secondary: {
          color: "purple",
        },
      },
    },
  },
  palette: {
    mode: "dark",
    primary: {
      main: "hsl(8,71%,28%)" /* burgundy mapped to link */,
    },
    secondary: {
      main: "hsl(61,78%,26%)" /* brown */,
    },
  },
});

const lightTheme = createTheme({
  components: {
    MuiListItemText: {
      styleOverrides: {
        primary: {
          color: "aqua",
        },
        secondary: {
          color: "grey",
        },
      },
    },
  },
  palette: {
    mode: "light",
    primary: {
      main: "hsl(8,10%,18%)" /* burgundy mapped to link */,
    },
    secondary: {
      main: "hsl(61,18%,26%)" /* brown */,
    },
  },
});

export function CustomThemeProvider({ children }) {
  const [dark, setDark] = React.useState(false);

  function toggleTheme() {
    console.log("toggleTheme :: from dark = " + dark);
    if (dark === true) {
      setDark(false);
    } else {
      setDark(true);
    }
  }

  const theme = React.useMemo(() => {
    if (dark === true) {
      return createTheme(darkTheme);
    }
    return createTheme(lightTheme);
  }, [dark]);

  return (
    <CustomThemeContext.Provider value={toggleTheme}>
      <ThemeProvider theme={theme}>{children}</ThemeProvider>
    </CustomThemeContext.Provider>
  );
}

export function useToggleTheme() {
  const context = useContext(CustomThemeContext);
  if (context === undefined) {
    throw new Error(
      "useCustomThemeContext must be used within an CustomThemeProvider"
    );
  }
  return context;
}

Root组件

import React from "react";
import { CustomThemeProvider } from "@site/src/components/MuiTheme";
import App from "@site/src/components/App";

export default function Root({ children }) {
  return (
    <>
      <CustomThemeProvider>
        <App children={children}></App>
      </CustomThemeProvider>
    </>
  );
}

App组件

import React from "react";

export default function App(props) {
  return <React.Fragment>{props.children}</React.Fragment>;
}

错误原因分析

原代码的核心问题在于:ColorModeToggleWrapper组件每次渲染时都会直接调用muiToggle()(即toggleTheme函数),该函数会修改CustomThemeProvider中的dark状态,触发组件重渲染;重渲染后又会再次调用muiToggle(),形成无限更新循环,最终导致React抛出最大更新深度超限的错误。

修正方案

我们需要让MaterialUI的主题状态跟随Docusaurus的主题值同步变化,而非通过翻转函数手动切换,具体修改如下:

1. 重构React Context(MuiTheme)

修改Context,暴露主题状态和状态设置函数,而非仅提供翻转函数,同时初始化时匹配Docusaurus的初始主题:

import React, { useContext } from "react";
import { createTheme, ThemeProvider } from "@mui/material/styles";

const CustomThemeContext = React.createContext({
  dark: false,
  setDark: () => {}
});

// 保留原有的darkTheme和lightTheme定义
const darkTheme = createTheme({
  components: {
    MuiListItemText: {
      styleOverrides: {
        primary: {
          color: "orange",
        },
        secondary: {
          color: "purple",
        },
      },
    },
  },
  palette: {
    mode: "dark",
    primary: {
      main: "hsl(8,71%,28%)" /* burgundy mapped to link */,
    },
    secondary: {
      main: "hsl(61,78%,26%)" /* brown */,
    },
  },
});

const lightTheme = createTheme({
  components: {
    MuiListItemText: {
      styleOverrides: {
        primary: {
          color: "aqua",
        },
        secondary: {
          color: "grey",
        },
      },
    },
  },
  palette: {
    mode: "light",
    primary: {
      main: "hsl(8,10%,18%)" /* burgundy mapped to link */,
    },
    secondary: {
      main: "hsl(61,18%,26%)" /* brown */,
    },
  },
});

export function CustomThemeProvider({ children }) {
  const [dark, setDark] = React.useState(false);

  // 初始化时读取Docusaurus存储的主题,保持初始状态一致
  React.useEffect(() => {
    const savedTheme = localStorage.getItem('theme') || 'light';
    setDark(savedTheme === 'dark');
  }, []);

  const theme = React.useMemo(() => {
    return dark ? createTheme(darkTheme) : createTheme(lightTheme);
  }, [dark]);

  return (
    <CustomThemeContext.Provider value={{ dark, setDark }}>
      <ThemeProvider theme={theme}>{children}</ThemeProvider>
    </CustomThemeContext.Provider>
  );
}

export function useThemeContext() {
  const context = useContext(CustomThemeContext);
  if (context === undefined) {
    throw new Error(
      "useThemeContext must be used within an CustomThemeProvider"
    );
  }
  return context;
}

2. 修正ColorModeToggleWrapper组件

使用useEffect监听Docusaurus主题值的变化,同步更新MaterialUI的主题状态:

import React, { useEffect } from "react";
import ColorModeToggle from "@theme-original/ColorModeToggle";
import { useThemeContext } from "@site/src/components/MuiTheme";

export default function ColorModeToggleWrapper(props) {
  const { value: docusaurusTheme } = props;
  const { setDark } = useThemeContext();

  // 当Docusaurus主题变化时,同步设置MUI主题
  useEffect(() => {
    setDark(docusaurusTheme === 'dark');
  }, [docusaurusTheme, setDark]);

  return <ColorModeToggle {...props} />;
}

3. Root与App组件保持不变

Root和App组件无需修改,继续使用原代码即可。

效果验证

修改完成后,切换Docusaurus的明暗开关时,MaterialUI的主题会自动同步切换,且不会再出现无限更新循环的错误。

内容的提问来源于stack exchange,提问作者anon_dcs3spp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 18:27:18