是否可以在不出现常见闪存问题的情况下成功实现页面主题初始化,同时避免任何水合不匹配错误?
到目前为止,我的解决方案是在整个页面渲染之前执行 JavaScript,通过在
<script>
标签中使用 <head>
来检查用户 localStorage
,然后应用主题。
这工作得很好,除了我在控制台中收到水合错误。这显然是因为
localStorage
只能在客户端进行检查,因此服务器渲染的 HTML 和客户端渲染的 HTML 永远不会匹配。
我只是忽略这个控制台错误,但是水合作用不匹配是否表明 React 必须丢弃服务器渲染的 HTML 并在客户端上重新渲染树?由于担心性能下降,我对不得不接受这一点并不是特别满意。
我的研究似乎唯一的其他解决方案是通过 cookie 传递用户选择的主题,我不太喜欢这种方式,因为
localStorage
更持久 - 或者保存用户首选项服务器端,这似乎是一个为了避免半秒的闪光,这一切都有点不必要。
真的没有办法使用
localStorage
来做到这一点吗?我的主要问题更侧重于避免这种水合作用问题。我已经有了一个有效的解决方案,但我不想避免水合不匹配。我能想到的最好办法就是设置一个默认的 Tailwind 主题类,例如 light
到 <html>
标签。这样,我可以通过确保水合错误仅在主题为深色时发生而不是总是出现不匹配来消除一半的问题。想要得到另一半,却无法逻辑地思考如何去做。也许有一些聪明的方法?
代码如下:
import type { Metadata } from "next";
import "./globals.css";
import NavBar from "./components/NavBar/NavBar";
import Footer from "./components/Footer/Footer";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const initTheme = `
(function() {
const theme = localStorage.getItem("theme") || "light";
document.documentElement.classList.remove('light', 'dark');
document.documentElement.classList.add(theme);})();
`;
return (
<html lang="en" className="light">
<head>
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
<script dangerouslySetInnerHTML={{ __html: initTheme }} />
</head>
<body>
<NavBar></NavBar>
{children}
<Footer></Footer>
</body>
</html>
);
}
这个套餐怎么样?
https://www.npmjs.com/package/next-themes
为了摆脱页面刷新期间发生的闪烁效果,我们可以使用这个库。
安装后,我们制作一个主题提供程序并将其注入到我们的布局中
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({ children }: { children: React.ReactNode }) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
>
{children}
</NextThemesProvider>
)
}
export default function RootLayout({
children,
params,
}: {
children: React.ReactNode
params: any
}) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
)
}
每当我们想要更改主题时,我们都会使用 useTheme 函数。这是您在应用程序的某些部分可能需要的切换模式组件,以向用户提供主题功能。
"use client"
import React from "react"
import { useTheme } from "next-themes"
import { Switch } from "@headlessui/react"
export interface SwitchDarkModeProps {
className?: string
}
const SwitchDarkMode: React.FC<SwitchDarkModeProps> = ({
className = "",
}) => {
const { setTheme, resolvedTheme } = useTheme()
const toggleTheme = () => {
if (resolvedTheme === "dark")
setTheme("light")
else
setTheme("dark")
}
return (
<div className="inline-flex">
<span className="sr-only">Enable dark mode</span>
<Switch
checked={resolvedTheme === "dark"}
onChange={toggleTheme}
className={`${resolvedTheme === "dark" ? "bg-teal-900" : "bg-teal-600"}
relative inline-flex h-[22px] w-[42px] shrink-0 cursor-pointer rounded-full border-4 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75`}
>
<span className="sr-only">Enable dark mode</span>
<span
aria-hidden="true"
className={`${resolvedTheme === "dark" ? "translate-x-5" : "translate-x-0"}
pointer-events-none inline-block h-[14px] w-[14px] transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out`}
/>
</Switch>
</div>
)
}
export default SwitchDarkMode2