React 主题切换与暗色模式:用 CSS 变量 + Context 把主题做成一等公民
test12026-10-021 次阅读
为什么主题不该写死在组件里
很多项目把颜色直接写进组件样式,做到一半要加暗色模式,只能全局搜索替换,还容易漏。更稳的做法是把所有颜色收敛到 CSS 变量,组件只消费变量;主题切换只是切换 :root 上的一组变量。React 负责把当前主题存进 Context,组件用 useTheme() 取用,完全不关心具体色值。
第一步:定义两套变量
在全局样式里放两份变量,暗色主题挂在 [data-theme="dark"] 上。组件里一律用 var(--color-bg),不直接写颜色。
:root {
--color-bg: #ffffff;
--color-fg: #1f2328;
--color-accent: #2563eb;
--color-border: #e5e7eb;
}
[data-theme="dark"] {
--color-bg: #0d1117;
--color-fg: #e6edf3;
--color-accent: #58a6ff;
--color-border: #30363d;
}
第二步:用 Context 管理主题
Context 持有当前主题与切换函数,初始值优先读 localStorage,其次跟随系统 prefers-color-scheme。切换时同步写回 data-theme 与存储,保证刷新不丢。
import { createContext, useContext, useEffect, useState } from "react";
type Theme = "light" | "dark";
const ThemeContext = createContext<{ theme: Theme; toggle: () => void } | null>(null);
function getInitialTheme(): Theme {
const saved = localStorage.getItem("theme");
if (saved === "light" || saved === "dark") return saved;
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>(getInitialTheme);
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem("theme", theme);
}, [theme]);
const toggle = () => setTheme((t) => (t === "dark" ? "light" : "dark"));
return <ThemeContext.Provider value={{ theme, toggle }}>{children}</ThemeContext.Provider>;
}
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useTheme 必须在 ThemeProvider 内使用");
return ctx;
}
第三步:组件只消费变量
组件样式通过 CSS 变量取色,切换主题时浏览器自动重算,无需 React 重渲染任何节点。
import { useTheme } from "./theme";
export function Toolbar() {
const { theme, toggle } = useTheme();
return (
<header style={{ background: "var(--color-bg)", color: "var(--color-fg)", borderBottom: "1px solid var(--color-border)" }}>
<span>当前:{theme}</span>
<button onClick={toggle} style={{ color: "var(--color-accent)" }}>切换主题</button>
</header>
);
}
工程要点
- 变量只定义在
:root与[data-theme="dark"]两层,主题越多就加一层选择器,组件零改动。 - 用
useEffect把主题写到document.documentElement,避免 SSR 首屏闪烁,可在服务端按 cookie 提前注入data-theme。 - 需要"跟随系统"的选项时,把主题状态扩展为
"light" | "dark" | "system",system 下用matchMedia监听变化。 - 不要给主题色加
transition到全局,只对需要平滑过渡的元素加,否则切换时整页闪一下。
T
test1
文章作者
为什么主题不该写死在组件里 很多项目把颜色直接写进组件样式,做到一半要加暗色模式,只能全局搜索替换,还容易漏。更稳的做...
- 分类
- 技术
- 发布时间
- 2026-10-02
- 字数
- 约 2415 字
- 阅读
- 1 次
