ThemeProvider next-themes入门介绍

半兽人 发表于: 2025-03-27   最后更新时间: 2025-03-27 10:22:20  
{{totalSubscript}} 订阅, 47 游览

在 Next.js 中使用 next-themesThemeProvider 是一个非常方便的方式来实现主题切换功能,例如支持浅色模式、深色模式或系统偏好主题。以下是详细的用法说明:


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>

注意事项

  1. Hydration 警告: 添加 suppressHydrationWarning<html> 标签,避免服务端和客户端渲染不一致的警告。
  2. 客户端渲染: useTheme 只能在客户端运行,因此需要检查组件是否已挂载。
  3. 生产环境: 在开发模式下可能会有闪烁问题,但在生产环境中 next-themes 会确保无闪烁加载。

通过以上步骤,你就可以在 Next.js 中使用 ThemeProvidernext-themes

示例

<ThemeProvider
  attribute='data-theme'
  forcedTheme='light'
  defaultTheme='light' // TODO: change to 'system' when dark mode ready
  enableSystem
  disableTransitionOnChange
>
    {children}
</ThemeProvider>

这是一个比较有特点的使用方式,接下来我们分析一下:

参数逐一解析

  1. 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 选择器使用。
  2. forcedTheme='light'

    • 含义: 强制所有页面使用指定的主题(这里是 "light"),无论用户的偏好或代码中的动态切换逻辑如何。这会覆盖 defaultThemeenableSystem 的效果。
    • 与我的示例的区别: 我的示例中没有使用 forcedTheme,允许用户通过 setTheme 或系统偏好动态切换主题。你这里的设置表明当前项目可能只支持浅色模式,或者开发者希望暂时锁定主题。
    • 用途: 在开发阶段或特定页面强制使用某一主题,常用于测试或未完成多主题支持时。
  3. defaultTheme='light'

    • 含义: 指定默认主题为 "light",即在没有用户偏好或系统设置的情况下使用的主题。注释中提到未来计划改为 "system",表明开发者可能在准备深色模式。
    • 与我的示例的区别: 我的示例中使用的是 defaultTheme="system",默认跟随系统主题(例如用户设备设置为深色模式时自动切换)。你这里固定为 "light",可能因为深色模式尚未实现。
    • 用途: 设置初始主题,避免页面加载时无主题状态。
  4. enableSystem

    • 含义: 启用系统主题检测,允许 next-themes 根据用户的设备设置(prefers-color-scheme)自动切换主题。
    • 与我的示例的区别: 我的示例中也使用了 enableSystem,这点相同。但在你的代码中,由于 forcedTheme="light" 的存在,enableSystem 的效果被覆盖,除非移除 forcedTheme
    • 用途: 提供与操作系统主题同步的能力,提升用户体验。
  5. disableTransitionOnChange

    • 含义: 禁用主题切换时的 CSS 过渡动画。默认情况下,next-themes 会为主题切换添加一个平滑的过渡效果(例如颜色渐变),设置此参数后,切换会立即生效。
    • 与我的示例的区别: 我的示例中没有使用这个参数,默认允许过渡动画。你这里可能是为了避免切换时的闪烁或性能问题。
    • 用途: 控制主题切换的视觉效果,适用于需要快速、无动画切换的场景。

与我提供的代码的总体差异

参数 你的代码 我的代码 主要差异
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: 可能是为了解决开发中的闪烁问题,或者开发者更喜欢即时切换的效果。
更新于 2025-03-27

查看React更多相关的文章或提一个关于React的问题,也可以与我们一起分享文章