gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-05 01:54:23 24 4
gpt4 key购买 nike

我在我的 nextjs 应用程序中使用 mui5(Material Ui)。我正在尝试实现 暗模式。一切顺利。我想要一个功能,如果任何用户切换黑暗模式,那么它将被保存在本地存储中。然后当我刷新页面时,它会根据本地存储的值自动从本地存储和事件暗或亮模式获取值。如果用户第一次访问该站点,那么它应该会自动激活系统偏好模式。我的意思是如果本地存储中没有任何值,那么它应该自动激活系统偏好模式。我该怎么做。

这是我的代码-

_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,
};

工具按钮-

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}
/>

最佳答案

在这里,您可以看到我使用 Next.js 和 Material UI (5) 完成的示例:

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

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

创建所需的 2 个主题:

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

darkTheme.ts

import { createTheme } from '@mui/material/styles'
const darkTheme = createTheme({
palette: {
mode: 'dark',
},
})
export default darkTheme

lightTheme.ts

import { createTheme } from '@mui/material/styles'
const lightTheme = createTheme({
palette: {
mode: 'light',
},
})
export default lightTheme

创建切换器按钮:

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

interface ThemeSwitcherButtonProps extends IconButtonProps { }
const ThemeSwitcherButton = ({ ...rest }: ThemeSwitcherButtonProps) => {
const { themeMode, toggleTheme } = useThemeContext()
return (
<Tooltip
title={themeMode === 'light' ? `Switch to dark mode` : `Switch to light mode`}
>
<IconButton
{...rest}
onClick={toggleTheme}
>
{themeMode === 'light' ? <DarkModeOutlined /> : <LightModeRounded />}
</IconButton>
</Tooltip>
)
}
export default ThemeSwitcherButton

创建ThemeContext、ThemeProvider,并使用ThemeContext:

我们使用 material ui 库中的 useMediaQuery 来检查浏览器的偏好模式。此 Hook 适用于客户端渲染和 ssr。

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

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

ThemeContext.tsx

import { createContext, ReactNode, useContext } from 'react'
import { ThemeProvider as MuiThemeProvider, useMediaQuery } from '@mui/material'

import lightTheme from '@/themes/light'
import darkTheme from '@/themes/dark'
import useLocalStorage from '@/react/hooks/useLocalStorage'

const DARK_SCHEME_QUERY = '(prefers-color-scheme: dark)'

type ThemeMode = 'light' | 'dark'
interface ThemeContextType {
themeMode: ThemeMode
toggleTheme: () => void
}
const ThemeContext = createContext<ThemeContextType>({} as ThemeContextType)
const useThemeContext = () => useContext(ThemeContext)

const ThemeProvider = ({ children }: { children: ReactNode }) => {
const isDarkOS = useMediaQuery(DARK_SCHEME_QUERY)
const [themeMode, setThemeMode] = useLocalStorage<ThemeMode>('themeMode', isDarkOS ? 'light' : 'dark')

const toggleTheme = () => {
switch (themeMode) {
case 'light':
setThemeMode('dark')
break
case 'dark':
setThemeMode('light')
break
default:
}
}

return (
<ThemeContext.Provider value={{ themeMode, toggleTheme }}>
<MuiThemeProvider theme={themeMode === 'light' ? lightTheme : darkTheme}>
{children}
</MuiThemeProvider>
</ThemeContext.Provider>
)
}

export {
useThemeContext,
ThemeProvider
}

_app.tsx

export default function MyApp(props: MyAppProps) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props
const getLayout = Component.getLayout ?? ((page) => page)

return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
{getLayout(<Component {...pageProps} />)}
</ThemeProvider>
</CacheProvider>
)
}

创建 useLocalStorage Hook :

我从 here 获取源代码,并对其进行了一些修改,以便在 ssr 和客户端呈现不匹配的情况下可以与 Next.js 一起正常工作。

useLocalStorage 还使用另一个 useEventListener Hook ,以同步所有其他打开的选项卡中值的更改。

使用本地存储.tsx

// edited from source: https://usehooks-ts.com/react-hook/use-local-storage
// to support ssr in Next.js
import { Dispatch, SetStateAction, useEffect, useState } from 'react'

import useEventListener from '@/react/hooks/useEventListener'


type SetValue<T> = Dispatch<SetStateAction<T>>

function useLocalStorage<T>(key: string, initialValue: T): [T, SetValue<T>] {
// Read local storage the parse stored json or return initialValue
const readStorage = (): T => {
if (typeof window === 'undefined') {
return initialValue
}
try {
const item = window.localStorage.getItem(key)
return item ? (parseJSON(item) as T) : initialValue
} catch (error) {
console.warn(`Error reading localStorage key “${key}”:`, error)
return initialValue
}
}

// Persists the new value to localStorage.
const setStorage: SetValue<T> = value => {
if (typeof window == 'undefined') {
console.warn(
`Tried setting localStorage key “${key}” even though environment is not a client`,
)
}
try {
// Allow value to be a function so we have the same API as useState
const newValue = value instanceof Function ? value(state) : value

// Save to local storage
window.localStorage.setItem(key, JSON.stringify(newValue))

// We dispatch a custom event so every useLocalStorage hook are notified
window.dispatchEvent(new Event('local-storage'))
} catch (error) {
console.warn(`Error setting localStorage key “${key}”:`, error)
}
}

// State to store the value
const [state, setState] = useState<T>(initialValue)

// Once the component is mounted, read from localStorage and update state.
useEffect(() => {
setState(readStorage())
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

useEffect(() => {
setStorage(state)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state])

const handleStorageChange = () => {
setState(readStorage())
}

// this only works for other documents, not the current one
useEventListener('storage', handleStorageChange)

// this is a custom event, triggered in writeValueToLocalStorage
// See: useLocalStorage()
useEventListener('local-storage', handleStorageChange)

return [state, setState]
}

export default useLocalStorage

// A wrapper for "JSON.parse()"" to support "undefined" value
function parseJSON<T>(value: string | null): T | undefined {
try {
return value === 'undefined' ? undefined : JSON.parse(value ?? '')
} catch (error) {
console.log('parsing error on', { value })
return undefined
}
}

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

代码

这个例子的所有代码都可以在我的github repository中找到可以用作带有 Typescript、Nextjs 和 Material UI 的项目的起点。

您也可以尝试使用有效的 example here .

关于reactjs - Material ui 深色模式在页面刷新时重置?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70657811/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com