React+TailwindCSS快速实现暗黑模式切换
使用 React Context API、React hooks 和 TailwindCSS 的 Dark Mode 快速实现网页浅色模式和深色模式之间的切换。此教程需要对 React 的 Context Api 有一定的了解,如果您还不了解,可以前往 react.dev 学习。
使用 React Context API、React hooks 和 TailwindCSS 的 Dark Mode 快速实现网页浅色模式和深色模式之间的切换。
此教程需要对 React 的 Context Api 有一定的了解,如果您还不了解,可以前往 使用 Context 深层传递参数 学习。
通过这篇文章,您将学到:
- 使用 React Context 和 localStorage 共享全局状态。
- 暗黑模式切换:浅色、深色、系统。
起步
使用 Vite 创建 React 项目:
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
安装依赖、运行项目:
cd react-dark-mode
pnpm install
pnpm run dev
安装 Tailwind CSS、生成 tailwind.config.js
和 postcss.config.js
文件:
pnpm install -D tailwindcss postcss autoprefixer
pnpm tailwindcss init -p
修改 tailwind.config.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
文件,定义一些基本的样式:
@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
:
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
}
- 首先从 localStorege 中获取主题,如果没有找到,则使用默认主题。
- 监听主题状态的变化。每当主题改变时,更新 html 的 class。如果主题为 system,则根据 prefers-color-scheme 来检测系统的主题色设置,给 html 添加对应的 class。
setTheme
函数将新的主题存储在localStorage中,并更新主题状态。- 通过
ThemeProviderContext.Provider
组件将主题状态和setTheme
函数传递给其子组件。
useTheme
是一个自定义Hook,它可以在任何函数组件中访问主题上下文。如果它在ThemeProvider
之外被调用,将抛出一个错误。
修改 main.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 图标库:
pnpm install lucide-react
新建 /src/components/ThemeToggle.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
文件:
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;
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)