Mat*_*ori 6 javascript typescript reactjs next.js
我正在尝试使用 Context API 创建一个主题提供程序来设置应用程序主题,这只是一个classNameon body。
上下文非常简单。在主题状态的惰性初始化器上,首先,我检查主题是否打开localStorage。如果主题不在本地存储中,那么我会根据用户系统首选项设置主题。
'use client'
import { createContext, useEffect, useState } from 'react'
import {
type ThemeContextData,
type ThemeProviderProps,
type Themes
} from './types'
import { darkTheme, lightTheme } from '@/styles/themes.css'
export const ThemeContext = createContext<ThemeContextData>(
{} as ThemeContextData
)
export function ThemeProvider({
children,
className
}: ThemeProviderProps): JSX.Element {
const [theme, setTheme] = useState<Themes>(() => {
if (typeof window !== 'undefined') {
const isThemeOnLocalStorage = window.localStorage.getItem(
'@matheussartori/theme'
)
if (isThemeOnLocalStorage) {
return isThemeOnLocalStorage as Themes
}
const isSystemThemeDark = window.matchMedia(
'(prefers-color-scheme: dark)'
).matches
if (isSystemThemeDark) {
return 'dark'
}
}
return 'light'
})
useEffect(() => {
if (typeof window !== 'undefined') {
window.localStorage.setItem('@matheussartori/theme', theme)
}
}, [theme])
const themeClassNames = {
light: lightTheme,
dark: darkTheme
}
const classNames = className
? `${themeClassNames[theme]} ${className}`
: themeClassNames[theme]
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<body className={classNames}>{children}</body>
</ThemeContext.Provider>
)
}
Run Code Online (Sandbox Code Playgroud)
在我的中layout.tsx,我也得到了以下代码:
import { Inter } from 'next/font/google'
import '@/styles/global.css'
import { ThemeProvider } from '@/contexts/themes/ThemeProvider'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'lorem',
description:
'lorem'
}
export default function RootLayout({
children
}: {
children: React.ReactNode
}): JSX.Element {
return (
<html lang="en">
<ThemeProvider className={`${inter.className}`}>{children}</ThemeProvider>
</html>
)
}
Run Code Online (Sandbox Code Playgroud)
发生的问题可能是由于 ThemeProvider 上的服务器渲染造成的。浏览器中显示的错误是:
警告:道具
className不匹配。服务器:“themes_lightTheme__1ra6bom9 __className_0ec1f4”客户端:“themes_darkTheme__1ra6bom0 __className_0ec1f4”
由于服务器不知道window/ localStorage,显然,在补水时,服务器说主题必须是浅色的,但它是深色的。
有没有更好的方法来实现这种方法?
客户端和服务器上的初始渲染应该相同。每当您违反此规则时,您都会收到水合localStorage错误,就像您的情况一样,服务器上不会出现水合错误,但它会出现在浏览器上,从而导致不同的初始状态。
您可以在以下内容中移动实例化主题的逻辑useEffect:
"use client";
import { createContext, useEffect, useState } from "react";
import { type ThemeContextData, type ThemeProviderProps, type Themes } from "./types";
import { darkTheme, lightTheme } from "@/styles/themes.css";
export const ThemeContext = createContext<ThemeContextData>({} as ThemeContextData);
export function ThemeProvider({ children, className }: ThemeProviderProps): JSX.Element {
const [theme, setTheme] = useState<Themes | null>(null);
useEffect(() => {
const isThemeOnLocalStorage = window.localStorage.getItem("@matheussartori/theme");
if (isThemeOnLocalStorage) {
setTheme(isThemeOnLocalStorage);
return;
}
const isSystemThemeDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
if (isSystemThemeDark) {
setTheme("dark");
return;
}
setTheme("light");
}, []);
useEffect(() => {
if (theme) {
window.localStorage.setItem("@matheussartori/theme", theme);
}
}, [theme]);
const themeClassNames = {
light: lightTheme,
dark: darkTheme,
};
const classNames = className ? `${themeClassNames[theme]} ${className}` : themeClassNames[theme];
// This is so you don't show anything before the theme is fully set. You could set a loader or something, or even remove the if statement
if (!theme) {
return null;
}
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<body className={classNames}>{children}</body>
</ThemeContext.Provider>
);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1570 次 |
| 最近记录: |