Material ui 深色模式在页面刷新时重置?

3 reactjs material-ui next.js

mui5(Material Ui)我正在我的应用程序中使用nextjs。我正在努力实施dark mode。一切进展顺利。我想要一个功能,如果任何用户切换黑暗模式,那么它将保存在本地存储中。然后,当我刷新页面时,它会自动从本地存储获取值,并根据本地存储的值激活深色或浅色模式。如果用户第一次访问该站点,那么它应该自动激活系统首选项模式。我的意思是,如果本地存储中没有值,那么它应该自动激活系统首选项模式。我怎样才能做到这一点。

这是我的代码-

_app.js

export default function MyApp(props) {
  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
  const [mode, setMode] = React.useState("light");
  const colorMode = React.useMemo(
    () => ({
      // The dark mode switch would invoke this method
      toggleColorMode: () => {
        setMode((prevMode) =>
          prevMode === 'light' ? 'dark' : 'light',
        );
      },
    }),
    [],
  );

  // Update the theme only if the mode changes
  const muiTheme = React.useMemo(() => createTheme(theme(mode)), [mode]);
  return (
    <ColorModeContext.Provider value={colorMode}>
      <CacheProvider value={emotionCache}>
        <Head>
          <meta name="viewport" content="initial-scale=1, width=device-width" />
        </Head>

        <ThemeProvider theme={muiTheme}>
          <CssBaseline />
          <Component {...pageProps} />
        </ThemeProvider>
      </CacheProvider>
    </ColorModeContext.Provider>
  );
}

MyApp.propTypes = {
  Component: PropTypes.elementType.isRequired,
  emotionCache: PropTypes.object,
  pageProps: PropTypes.object.isRequired,
};
Run Code Online (Sandbox Code Playgroud)

切换按钮-

const theme = useTheme();
const colorMode = useContext(ColorModeContext);

<FormControlLabel
   control={<MaterialUISwitch sx={{ m: 1 }} checked={theme.palette.mode === 'dark' ? false : true} />}
   label=""
   sx={{ mx: "0px" }}
   onClick={colorMode.toggleColorMode}
/>
Run Code Online (Sandbox Code Playgroud)

gio*_*ine 6

您可以在此处查看我使用 Next.js 和 Material UI (5) 所做的示例:

\n
    \n
  • 有 2 个可用主题:lightTheme 和 darkTheme。
  • \n
  • 有一个 ThemeSwitcherButton 组件,这样我们就可以在两个主题之间切换。
  • \n
  • 创建一个新的 ThemeProvider 和 ThemeContext 来存储选定的主题模式值,提供读取和更改它的访问权限。
  • \n
  • 使用 useLocalStorage 挂钩将用户的首选项存储在本地存储上。
  • \n
  • 如果没有存储值,请使用 Material UI useMediaQuery 挂钩加载从浏览器首选项读取的主题模式。
  • \n
\n

我正在使用 Typescript,但如果你使用纯 JavaScript 也没关系

\n

创建所需的 2 个主题:

\n

我们将有 2 个文件来独立修改特定属性。

\n

黑暗主题.ts

\n
import { createTheme } from \'@mui/material/styles\'\nconst darkTheme = createTheme({\n    palette: {\n        mode: \'dark\',\n    },\n})\nexport default darkTheme\n
Run Code Online (Sandbox Code Playgroud)\n

光主题.ts

\n
import { createTheme } from \'@mui/material/styles\'\nconst lightTheme = createTheme({\n    palette: {\n        mode: \'light\',\n    },\n})\nexport default lightTheme\n
Run Code Online (Sandbox Code Playgroud)\n

创建切换器按钮:

\n

这里重要的是从上下文中检索的值和功能,按钮样式或图标可以是任何东西。

\n
interface ThemeSwitcherButtonProps extends IconButtonProps { }\nconst ThemeSwitcherButton = ({ ...rest }: ThemeSwitcherButtonProps) => {\n    const { themeMode, toggleTheme } = useThemeContext()\n    return (\n        <Tooltip\n            title={themeMode === \'light\' ? `Switch to dark mode` : `Switch to light mode`}\n        >\n            <IconButton\n                {...rest}\n                onClick={toggleTheme}\n            >\n                {themeMode === \'light\' ? <DarkModeOutlined /> : <LightModeRounded />}\n            </IconButton>\n        </Tooltip>\n    )\n}\nexport default ThemeSwitcherButton\n
Run Code Online (Sandbox Code Playgroud)\n

创建 ThemeContext、ThemeProvider 和 useThemeContext:

\n

我们使用useMediaQuerymaterial ui 库来检查浏览器的首选项模式。该钩子适用于客户端渲染和 ssr。

\n

我们还使用useLocalStorage钩子将状态保存在本地存储中,以便它被持久化。

\n

我们用这个新的Provider包装了原来的Material UI ThemeProvider(重命名为MuiThemeProvider),所以那么在_app文件中只需要一个Provider

\n

主题上下文.tsx

\n
import { createContext, ReactNode, useContext } from \'react\'\nimport { ThemeProvider as MuiThemeProvider, useMediaQuery } from \'@mui/material\'\n\nimport lightTheme from \'@/themes/light\'\nimport darkTheme from \'@/themes/dark\'\nimport useLocalStorage from \'@/react/hooks/useLocalStorage\'\n\nconst DARK_SCHEME_QUERY = \'(prefers-color-scheme: dark)\'\n\ntype ThemeMode = \'light\' | \'dark\'\ninterface ThemeContextType {\n    themeMode: ThemeMode\n    toggleTheme: () => void\n}\nconst ThemeContext = createContext<ThemeContextType>({} as ThemeContextType)\nconst useThemeContext = () => useContext(ThemeContext)\n\nconst ThemeProvider = ({ children }: { children: ReactNode }) => {\n    const isDarkOS = useMediaQuery(DARK_SCHEME_QUERY)\n    const [themeMode, setThemeMode] = useLocalStorage<ThemeMode>(\'themeMode\', isDarkOS ? \'light\' : \'dark\')\n\n    const toggleTheme = () => {\n        switch (themeMode) {\n            case \'light\':\n                setThemeMode(\'dark\')\n                break\n            case \'dark\':\n                setThemeMode(\'light\')\n                break\n            default:\n        }\n    }\n\n    return (\n        <ThemeContext.Provider value={{ themeMode, toggleTheme }}>\n            <MuiThemeProvider theme={themeMode === \'light\' ? lightTheme : darkTheme}>\n                {children}\n            </MuiThemeProvider>\n        </ThemeContext.Provider>\n    )\n}\n\nexport {\n    useThemeContext,\n    ThemeProvider\n}\n
Run Code Online (Sandbox Code Playgroud)\n

_app.tsx

\n
export default function MyApp(props: MyAppProps) {\n  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props\n  const getLayout = Component.getLayout ?? ((page) => page)\n\n  return (\n    <CacheProvider value={emotionCache}>\n      <Head>\n        <meta name="viewport" content="initial-scale=1, width=device-width" />\n      </Head>\n      <ThemeProvider>\n        {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}\n        <CssBaseline />\n        {getLayout(<Component {...pageProps} />)}\n      </ThemeProvider>\n    </CacheProvider>\n  )\n}\n
Run Code Online (Sandbox Code Playgroud)\n

创建 useLocalStorage 钩子:

\n

我从这里获取了源代码,并对其进行了一些修改,以便由于 ssr 和客户端渲染不匹配而可以与 Next.js 正常工作。

\n

useLocalStorage 还使用另一个 useEventListener 挂钩,以在打开的所有其他选项卡之间同步值的更改。

\n

useLocalStorage.tsx

\n
export default function MyApp(props: MyAppProps) {\n  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props\n  const getLayout = Component.getLayout ?? ((page) => page)\n\n  return (\n    <CacheProvider value={emotionCache}>\n      <Head>\n        <meta name="viewport" content="initial-scale=1, width=device-width" />\n      </Head>\n      <ThemeProvider>\n        {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}\n        <CssBaseline />\n        {getLayout(<Component {...pageProps} />)}\n      </ThemeProvider>\n    </CacheProvider>\n  )\n}\n
Run Code Online (Sandbox Code Playgroud)\n

useEventListener.tsx网络中的完全相同。

\n

代码

\n

此示例的所有代码都可以在我的github 存储库中找到,该存储库可以用作使用 Typescript、Nextjs 和 Material UI 的项目的起点。

\n

您还可以在这里尝试一个工作示例

\n