在 Next.js 中使用 next-themes
的 ThemeProvider
是一个非常方便的方式来实现主题切换功能,例如支持浅色模式、深色模式或系统偏好主题。以下是详细的用法说明:
1. 安装 next-themes
首先,确保你已经在项目中安装了 next-themes
。可以通过以下命令安装:
npm install next-themes
# 或
yarn add next-themes
2. 配置 ThemeProvider
next-themes
提供了一个 ThemeProvider
组件,你需要将其包裹在应用的根组件中。通常是在 app/layout.tsx
(Next.js 13+ App Router)或 pages/_app.tsx
(Pages Router)中。
示例代码(App Router - app/layout.tsx
)
// app/layout.tsx
import { ThemeProvider } from 'next-themes';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}
示例代码(Pages Router - pages/_app.tsx
)
// pages/_app.tsx
import { ThemeProvider } from 'next-themes';
import type { AppProps } from 'next/app';
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<Component {...pageProps} />
</ThemeProvider>
);
}
参数说明
attribute="class"
: 指定主题通过 HTML 的class
属性应用(例如<html class="dark">
)。也可以设置为data-theme
(例如<html data-theme="dark">
)。defaultTheme="system"
: 默认主题,设置为"system"
表示跟随系统偏好,也可以是"light"
或"dark"
。enableSystem
: 启用系统主题检测,允许根据用户设备的prefers-color-scheme
自动切换主题。
3. 使用 useTheme
钩子
next-themes
提供了一个 useTheme
钩子,用于在组件中获取当前主题或动态切换主题。由于它需要运行在客户端,你需要在组件顶部添加 "use client"
(App Router)。
示例代码(主题切换组件)
// components/ThemeSwitcher.tsx
"use client";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
export default function ThemeSwitcher() {
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
// 确保组件在客户端渲染后才显示,避免 hydration 不一致
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
return (
<div>
当前主题: {theme}
<button onClick={() => setTheme("light")}>浅色模式</button>
<button onClick={() => setTheme("dark")}>深色模式</button>
<button onClick={() => setTheme("system")}>系统模式</button>
</div>
);
}
在页面中使用
// app/page.tsx
import ThemeSwitcher from "@/components/ThemeSwitcher";
export default function Home() {
return (
<main>
<h1>欢迎使用 Next.js</h1>
<ThemeSwitcher />
</main>
);
}
4. 配置 Tailwind CSS(可选)
如果你使用 Tailwind CSS 来实现主题样式,可以在 tailwind.config.js
中启用 darkMode: 'class'
,以便根据 class
属性切换样式。
示例配置
// tailwind.config.js
module.exports = {
darkMode: "class", // 使用 class 切换主题
content: ["./app/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
示例 CSS
/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
background-color: white;
color: black;
}
.dark body {
background-color: black;
color: white;
}
5. 高级用法
自定义主题
你可以通过 themes
属性支持更多自定义主题:
<ThemeProvider themes={["light", "dark", "pink", "blue"]} attribute="data-theme">
{children}
</ThemeProvider>
然后在 CSS 中使用 [data-theme="pink"]
等选择器定义样式。
强制主题
如果某页面需要强制使用特定主题,可以在页面组件上设置 forcedTheme
:
// app/force-dark/page.tsx
export default function ForceDarkPage() {
return <h1>这是强制深色模式的页面</h1>;
}
ForceDarkPage.theme = "dark";
在 layout.tsx
中读取:
<ThemeProvider forcedTheme={process.browser ? ForceDarkPage.theme : null}>
{children}
</ThemeProvider>
注意事项
- Hydration 警告: 添加
suppressHydrationWarning
到<html>
标签,避免服务端和客户端渲染不一致的警告。 - 客户端渲染:
useTheme
只能在客户端运行,因此需要检查组件是否已挂载。 - 生产环境: 在开发模式下可能会有闪烁问题,但在生产环境中
next-themes
会确保无闪烁加载。
通过以上步骤,你就可以在 Next.js 中使用 ThemeProvider
和 next-themes
示例
<ThemeProvider
attribute='data-theme'
forcedTheme='light'
defaultTheme='light' // TODO: change to 'system' when dark mode ready
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
这是一个比较有特点的使用方式,接下来我们分析一下:
参数逐一解析
attribute='data-theme'
- 含义: 指定主题切换时修改的 HTML 属性为
data-theme
,而不是默认的class
。这意味着主题会以data-theme
属性的形式应用到<html>
标签上,例如<html data-theme="light">
。 - 与我的示例的区别: 我的示例中使用的是
attribute="class"
,主题会以类名形式应用,例如<html class="dark">
。选择data-theme
更适合某些 CSS 框架(例如 DaisyUI),或者当你希望避免类名冲突时。 - 用途: 用于告诉
next-themes
如何标记当前主题,供 CSS 选择器使用。
- 含义: 指定主题切换时修改的 HTML 属性为
forcedTheme='light'
- 含义: 强制所有页面使用指定的主题(这里是
"light"
),无论用户的偏好或代码中的动态切换逻辑如何。这会覆盖defaultTheme
和enableSystem
的效果。 - 与我的示例的区别: 我的示例中没有使用
forcedTheme
,允许用户通过setTheme
或系统偏好动态切换主题。你这里的设置表明当前项目可能只支持浅色模式,或者开发者希望暂时锁定主题。 - 用途: 在开发阶段或特定页面强制使用某一主题,常用于测试或未完成多主题支持时。
- 含义: 强制所有页面使用指定的主题(这里是
defaultTheme='light'
- 含义: 指定默认主题为
"light"
,即在没有用户偏好或系统设置的情况下使用的主题。注释中提到未来计划改为"system"
,表明开发者可能在准备深色模式。 - 与我的示例的区别: 我的示例中使用的是
defaultTheme="system"
,默认跟随系统主题(例如用户设备设置为深色模式时自动切换)。你这里固定为"light"
,可能因为深色模式尚未实现。 - 用途: 设置初始主题,避免页面加载时无主题状态。
- 含义: 指定默认主题为
enableSystem
- 含义: 启用系统主题检测,允许
next-themes
根据用户的设备设置(prefers-color-scheme
)自动切换主题。 - 与我的示例的区别: 我的示例中也使用了
enableSystem
,这点相同。但在你的代码中,由于forcedTheme="light"
的存在,enableSystem
的效果被覆盖,除非移除forcedTheme
。 - 用途: 提供与操作系统主题同步的能力,提升用户体验。
- 含义: 启用系统主题检测,允许
disableTransitionOnChange
- 含义: 禁用主题切换时的 CSS 过渡动画。默认情况下,
next-themes
会为主题切换添加一个平滑的过渡效果(例如颜色渐变),设置此参数后,切换会立即生效。 - 与我的示例的区别: 我的示例中没有使用这个参数,默认允许过渡动画。你这里可能是为了避免切换时的闪烁或性能问题。
- 用途: 控制主题切换的视觉效果,适用于需要快速、无动画切换的场景。
- 含义: 禁用主题切换时的 CSS 过渡动画。默认情况下,
与我提供的代码的总体差异
参数 | 你的代码 | 我的代码 | 主要差异 |
---|---|---|---|
attribute |
'data-theme' |
'class' |
你使用 data-theme 属性,我使用 class ,影响 CSS 选择器写法 |
forcedTheme |
'light' |
未设置 | 你强制使用浅色主题,我允许动态切换 |
defaultTheme |
'light' |
'system' |
你默认浅色,我默认跟随系统 |
enableSystem |
true |
true |
相同,但你因 forcedTheme 无效 |
disableTransitionOnChange |
true |
未设置(默认 false ) |
你禁用过渡动画,我保留默认动画效果 |
这些参数的实际用途
attribute='data-theme'
: 如果你使用像 DaisyUI 或其他支持data-theme
的 CSS 框架,这个设置很常见。CSS 示例:[data-theme="light"] { background-color: white; color: black; } [data-theme="dark"] { background-color: black; color: white; }
forcedTheme='light'
: 表明当前项目可能还在开发中,深色模式未就绪。通过强制浅色主题,避免用户看到未完成的深色样式。defaultTheme='light'
: 与forcedTheme
搭配,确保即使移除forcedTheme
,初始状态也是浅色。enableSystem
: 为将来支持系统主题做准备,一旦移除forcedTheme
,就能自动适配用户设备的深色/浅色偏好。disableTransitionOnChange
: 可能是为了解决开发中的闪烁问题,或者开发者更喜欢即时切换的效果。