React+TailwindCSS快速实现暗黑模式切换

使用 React Context API、React hooks 和 TailwindCSS 的 Dark Mode 快速实现网页浅色模式和深色模式之间的切换。

此教程需要对 React 的 Context Api 有一定的了解,如果您还不了解,可以前往 使用 Context 深层传递参数 学习。

通过这篇文章,您将学到:

  • 使用 React Context 和 localStorage 共享全局状态。
  • 暗黑模式切换:浅色、深色、系统。

起步

使用 Vite 创建 React 项目:

shell 复制代码
PS J:\react-project> pnpm create vite@latest
√ Project name: ... react-dark-mode
√ Select a framework: >> React
√ Select a variant: >> TypeScript + SWC

Scaffolding project in J:\react-project\react-dark-mode...

Done. Now run:

  cd react-dark-mode
  pnpm install
  pnpm run dev

安装依赖、运行项目:

shell 复制代码
cd react-dark-mode
pnpm install
pnpm run dev

安装 Tailwind CSS、生成 tailwind.config.jspostcss.config.js 文件:

csharp 复制代码
pnpm install -D tailwindcss postcss autoprefixer
pnpm tailwindcss init -p

修改 tailwind.config.js 文件:

js 复制代码
/** @type {import('tailwindcss').Config} */
export default {
  darkMode: "class",
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

darkMode 设置为 class,修改 content 添加模板文件路径。

修改 /src/index.css文件,定义一些基本的样式:

css 复制代码
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer {
  :root {
    --background: #ffffff;
    --foreground: #09090b;
    --border: #e3e3e7;
  }

  .dark {
    --background: #09090b;
    --foreground: #f9f9f9;
    --border: #27272a;
  }
}

@layer base {
  * {
    @apply box-border border-[--border];
  }

  body {
    @apply bg-[--background] text-[--foreground];
  }
}

创建 React Context

新建文件 /src/components/ThemeProvider.tsx

tsx 复制代码
import { createContext, useContext, useEffect, useState } from "react"

type Theme = "dark" | "light" | "system"

type ThemeProviderProps = {
  children: React.ReactNode
  defaultTheme?: Theme
  storageKey?: string
}

type ThemeProviderState = {
  theme: Theme
  setTheme: (theme: Theme) => void
}

const initialState: ThemeProviderState = {
  theme: "system",
  setTheme: () => null,
}

const ThemeProviderContext = createContext<ThemeProviderState>(initialState)

export function ThemeProvider({
  children,
  defaultTheme = "system",
  storageKey = "theme",
  ...props
}: ThemeProviderProps) {
  const [theme, setTheme] = useState<Theme>(
    () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
  )

  useEffect(() => {
    const root = window.document.documentElement

    root.classList.remove("light", "dark")

    if (theme === "system") {
      const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
        .matches
        ? "dark"
        : "light"

      root.classList.add(systemTheme)
      return
    }

    root.classList.add(theme)
  }, [theme])

  const value = {
    theme,
    setTheme: (theme: Theme) => {
      localStorage.setItem(storageKey, theme)
      setTheme(theme)
    },
  }

  return (
    <ThemeProviderContext.Provider {...props} value={value}>
      {children}
    </ThemeProviderContext.Provider>
  )
}

export const useTheme = () => {
  const context = useContext(ThemeProviderContext)

  if (context === undefined)
    throw new Error("useTheme必须在ThemeProvider中使用")

  return context
}
  1. 首先从 localStorege 中获取主题,如果没有找到,则使用默认主题。
  2. 监听主题状态的变化。每当主题改变时,更新 html 的 class。如果主题为 system,则根据 prefers-color-scheme 来检测系统的主题色设置,给 html 添加对应的 class。
  3. setTheme函数将新的主题存储在localStorage中,并更新主题状态。
  4. 通过ThemeProviderContext.Provider组件将主题状态和setTheme函数传递给其子组件。

useTheme是一个自定义Hook,它可以在任何函数组件中访问主题上下文。如果它在ThemeProvider之外被调用,将抛出一个错误。

修改 main.tsx

tsx 复制代码
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { ThemeProvider } from "./components/ThemeProvider.tsx";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <ThemeProvider defaultTheme="dark" storageKey="theme">
      <App />
    </ThemeProvider>
  </React.StrictMode>,
);

切换主题

安装 lucide-react 图标库:

shell 复制代码
pnpm install lucide-react

新建 /src/components/ThemeToggle.tsx 文件:

tsx 复制代码
import { Sun, Moon } from "lucide-react";
import { useTheme } from "./components/ThemeProvider";

const ThemeToggle = () => {
  const { theme, setTheme } = useTheme();
  return (
    <button
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
      className="inline-flex rounded-md p-2 hover:bg-zinc-100 dark:hover:bg-zinc-900"
    >
      <Sun
        size={20}
        className="rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
      />
      <Moon
        size={20}
        className="absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
      />
    </button>
  );
};

export default ThemeToggle;

引入 /src/App.tsx 文件:

tsx 复制代码
import ThemeToggle from "./components/ThemeToggle";

function App() {
  return (
    <div className="sapce-y-4 mx-auto max-w-xl py-4">
      <ThemeToggle />
      <div>
        <h1 className="py-2 text-xl font-bold">
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Velit, magni.
        </h1>
        <p>
          Lorem ipsum, dolor sit amet consectetur adipisicing elit. Sint enim
          impedit qui omnis cumque corporis reprehenderit similique quae fugiat
          magni aut suscipit ducimus vel magnam, officiis alias minus ut!
          Quisquam maiores et saepe omnis magni similique quidem cum dolor unde,
          dicta rerum provident illum molestiae vitae minus iure id debitis!
        </p>
      </div>
    </div>
  );
}

export default App;
相关推荐
screct_demo5 小时前
詳細講一下在RN(ReactNative)中,6個比較常用的組件以及詳細的用法
javascript·react native·react.js
光头程序员13 小时前
grid 布局react组件可以循数据自定义渲染某个数据 ,或插入某些数据在某个索引下
javascript·react.js·ecmascript
limit for me13 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者13 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架
VillanelleS16 小时前
React进阶之高阶组件HOC、react hooks、自定义hooks
前端·react.js·前端框架
傻小胖17 小时前
React 中hooks之useInsertionEffect用法总结
前端·javascript·react.js
如果'\'真能转义说21 小时前
TypeScript - 利用GPT辅助学习
gpt·学习·typescript
flying robot1 天前
React的响应式
前端·javascript·react.js
GISer_Jing2 天前
React+AntDesign实现类似Chatgpt交互界面
前端·javascript·react.js·前端框架
智界工具库2 天前
【探索前端技术之 React Three.js—— 简单的人脸动捕与 3D 模型表情同步应用】
前端·javascript·react.js