如何在 React 中创建暗黑模式组件

2025-05-24

如何在 React 中创建暗黑模式组件

本教程的所有代码都可以在此存储库中找到,并且本教程的视频版本如下所示。

目录

  1. 现场演示
  2. 先决条件
  3. 初始化项目
  4. 添加样式
  5. 添加切换按钮
  6. 创建 DarkMode 组件
  7. 添加测试(可选)
  8. 为应用程序添加暗黑模式
  9. 设置首选配色方案
  10. 总结

为 Web 应用提供暗黑模式已成为用户的期望,实现它的方法有很多。通常,最有效的方法是利用CSS 变量的强大功能。

在本教程中,我们将展示如何将整个暗模式功能捆绑到一个<DarkMode />组件中,您可以随身携带并放置在任何应用程序中。

这个组件不仅会在页面关闭或刷新后保留你选择的设置,还会尊重用户prefers-color-scheme在浏览器中的设置。太酷了!

让我们深入研究一下。

现场演示

在开始之前,我们先来看一下最终产品的演示,以便您了解本教程的预期内容。完成后,您将拥有自己的<DarkMode />组件,可以将其放入任何应用程序中以实现此功能。

先决条件

我假设您对 React有基本的了解。

您无需成为专家。实际上,我们没有单个状态变量,也没有任何钩子或生命周期方法。我们的目标(也应该始终如此)是尽量降低复杂性。此功能不需要它们。

我们将在本教程中使用Create React App,因为它是一种极其简单的方法,可以快速轻松地建立可在其上构建的 React 应用程序模板。

即使您选择不使用 CRA,您仍然可以继续学习本教程。我们将编写纯 CSS,但为了准确复制示例,您需要安装webpack并配置 CSS 加载器来支持importCSS 文件的语法。

如果您不使用,您可以简单地在您的 CSS 文件中webpack使用一个元素,而不是导入它们。<link>index.html

我们还将使用Typescript,这是我最近构建的每个 Web 项目的默认语言。即使你对 Typescript 不是很熟悉,也应该能够跟上,因为这些示例中显式输入的数量很少。

最后,我添加了一个部分,介绍如何使用React 测试库为组件添加测试。此部分是可选的。

初始化项目

如果您正在使用 CRA,则运行以下命令(如果您有自己的现有项目,则请忽略)



npx create-react-app dark-mode-example --template typescript


Enter fullscreen mode Exit fullscreen mode

添加样式

当应用程序加载时,它将按照以下优先级顺序确定暗/亮设置:

  1. 用户之前的切换设置
  2. 用户的浏览器偏好设置
  3. 灯光模式

我们将首先创建处理暗模式的 CSS。

src/DarkMode.css




/* 1 */
:root {
  --font-color: #333;
  --background-color: #eee;
  --link-color: cornflowerblue;
}

/* 2 */
[data-theme="dark"] {
  --font-color: #eee;
  --background-color: #333;
  --link-color: lightblue;
}

/* 3 */
body {
  background-color: var(--background-color);
  color: var(--font-color);
}

a {
  color: var(--link-color);
}


Enter fullscreen mode Exit fullscreen mode
  1. 选择:root器与代表 DOM 树的根元素匹配。您在此处放置的任何内容都将在应用程序的任何位置可用。在这里,我们将创建用于保存浅色主题颜色的CSS 变量。

  2. 这里我们设置了主题的颜色dark。使用属性选择器,我们可以定位任何带有data-theme="dark"属性的元素。这是一个自定义属性,我们将自己将其添加到<html>元素上。

  3. --background-color我们设置应用程序的背景颜色和文本颜色。这将始终是和变量的值--font-color。由于级联,这些变量的值将根据属性的设置时间而变化data-theme="dark"深色值在根值之后设置,因此如果选择器应用,这些变量的初始(浅色)值将被深色值覆盖。

注意,我还在这里添加了自定义链接颜色,它会根据主题的值而变化。您可以在此处添加任意数量的自定义颜色,并将它们全部通过亮/暗切换按钮控制。您可以尝试自己添加更多颜色!

添加切换按钮

接下来,我们将根据此示例创建一个自定义复选框输入,使其看起来像一个切换开关。

我不会评论这段 CSS 是如何工作的,因为它不在本教程的讨论范围内,而且与暗/亮模式无关。下面的样式只是为了覆盖默认 HTML 复选框的外观。

将它们添加到上述代码下方src/DarkMode.css

src/DarkMode.css



/* Custom Dark Mode Toggle Element */
.toggle-theme-wrapper {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
}

.toggle-theme-wrapper span {
  font-size: 28px;
}

.toggle-theme {
  position: relative;
  display: inline-block;
  height: 34px;
  width: 60px;
}

.toggle-theme input {
  display: none;
}

.slider {
  background-color: #ccc;
  position: absolute;
  cursor: pointer;
  bottom: 0;
  left: 0;
  right: 0;
  top: 0;
  transition: 0.2s;
}

.slider:before {
  background-color: #fff;
  bottom: 4px;
  content: "";
  height: 26px;
  left: 4px;
  position: absolute;
  transition: 0.4s;
  width: 26px;
}

input:checked + .slider:before {
  transform: translateX(26px);
}

input:checked + .slider {
  background-color: cornflowerblue;
}

.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}


Enter fullscreen mode Exit fullscreen mode

创建 DarkMode 组件

现在我们将创建我们的DarkMode组件。

首先我们只关注组件本身的结构,不关注事件或功能:

src/DarkMode.tsx



import "./DarkMode.css";

const DarkMode = () => {
  return (
    <div className="toggle-theme-wrapper">
      <span>☀️</span>
      <label className="toggle-theme" htmlFor="checkbox">
        <input
          type="checkbox"
          id="checkbox"
        />
        <div className="slider round"></div>
      </label>
      <span>🌒</span>
    </div>
  );
};

export default DarkMode;


Enter fullscreen mode Exit fullscreen mode

<input>元素将处理颜色主题的状态。当颜色主题被选中时checked,暗色模式处于活动状态;当未选中时,亮色模式处于活动状态。

如果您渲染此组件,您应该有一个美观的自定义切换按钮,但没有任何功能。

拨动开关

为了使我们的切换开关工作,我们必须攻击一些 Javascript 函数来处理onChange在切换复选框时触发的输入事件。

我们还需要决定页面或应用程序首次加载时默认显示的模式。这里有很多内容需要解释;示例下方带编号的注释中会解释具体发生了什么。

src/DarkMode.tsx



import "./DarkMode.css";
import { ChangeEventHandler } from "react";

// 1
const setDark = () => {

  // 2
  localStorage.setItem("theme", "dark");

  // 3
  document.documentElement.setAttribute("data-theme", "dark");
};

const setLight = () => {
  localStorage.setItem("theme", "light");
  document.documentElement.setAttribute("data-theme", "light");
};

// 4
const storedTheme = localStorage.getItem("theme");

const prefersDark =
  window.matchMedia &&
  window.matchMedia("(prefers-color-scheme: dark)").matches;

const defaultDark =
  storedTheme === "dark" || (storedTheme === null && prefersDark);

if (defaultDark) {
  setDark();
}

// 5
const toggleTheme: ChangeEventHandler<HTMLInputElement> = (e) => {
  if (e.target.checked) {
    setDark();
  } else {
    setLight();
  }
};

const DarkMode = () => {
  return (
    <div className="toggle-theme-wrapper">
      <span>☀️</span>
      <label className="toggle-theme" htmlFor="checkbox">
        <input
          type="checkbox"
          id="checkbox"

          // 6
          onChange={toggleTheme}
          defaultChecked={defaultDark}
        />
        <div className="slider round"></div>
      </label>
      <span>🌒</span>
    </div>
  );
};

export default DarkMode;


Enter fullscreen mode Exit fullscreen mode
  1. setDark我们创建了名为和 的函数setLight,它们的作用正如名称所描述的那样。我们希望这些函数尽可能简单。当调用它们时,我们希望应用能够切换到亮模式或暗模式。

  2. 这就是我们处理持久化的方式。使用localStorage可以让我们保存一个值,即使用户关闭应用或重新加载页面后,该值仍然会保留。每次设置亮色或暗色模式时,我们都将该值保存在theme的属性中localStorage

  3. 这里我们设置DOM 元素data-theme="dark"的 (或 light) 值<html>。这实际上会更新我们应用中的颜色。添加该属性后,[data-theme="dark"]CSS 中的选择器将变为活动状态,并设置深色变量(反之亦然)。

  4. 注释 4 下的部分是在页面加载完成,实际切换开关尚未使用时建立“初始”状态。如果存在,storedTheme则获取其值。检查媒体查询中用户浏览器设置的prefers-color-scheme。最后,会检查这两项,并根据我们在本教程开头建立的 3 条优先级规则,决定是否默认使用暗黑模式。如果结果为 true,我们会在组件渲染之前将应用设置为暗黑模式。(注意,我们之所以能这样做,是因为我们定位的是已经存在的属性。)localStorageprefersDarkdefaultDark<html>

  5. 这是我们编写的事件处理函数,用于捕获用户点击复选框时发生的更改事件。如果复选框处于选中状态,checked则启用暗色模式;否则,则启用亮色模式。

  6. 我们将刚刚创建的事件处理程序放置在onChange属性上,以便每次复选框发生变化时都会触发该事件处理程序。我们还使用defaultDark之前创建的布尔值来确定复选框是否默认启用。

添加测试(可选)

在将此组件添加到我们的应用程序之前,我们可以编写一些测试以确保它按预期工作。

Create React App预装了React Testing Library。它会自动获取.test.tsx你创建的任何文件。

src/DarkMode.test.tsx



import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import DarkMode from "./DarkMode";

// 1
test("renders dark mode component", () => {
  render(<DarkMode />);

  // 2
  const inputElement = screen.getByRole("checkbox") as HTMLInputElement;
  expect(inputElement).toBeInTheDocument();
});

// 3
test("toggles dark mode", () => {
  render(<DarkMode />);
  const inputElement = screen.getByRole("checkbox") as HTMLInputElement;

  // 4
  expect(inputElement.checked).toEqual(false);
  fireEvent.click(inputElement);
  expect(inputElement.checked).toEqual(true);

  // 5
  expect(document.documentElement.getAttribute("data-theme")).toBe("dark");
});


Enter fullscreen mode Exit fullscreen mode
  1. 一个简单的测试来确保组件呈现。

  2. 输入具有的角色,checkbox因此我们希望能够通过该角色找到元素。

  3. 测试以确保在复选框切换时组件确实激活了暗模式

  4. 使用测试库的 fireEvent功能,我们可以模拟点击输入。我们在点击之前断言它不应该被检查,然后在点击之后它应该被检查。

  5. 这个组件在设计上确实存在副作用,而这正是最终断言想要检测的。虽然该组件只是一个用于输入的小容器,但它被设计为将data-theme属性应用于根<html>元素。该元素可以通过 JavaScript 变量直接访问document.documentElement。我们在这里检查dark元素被点击后,值是否被应用到属性上。

如果使用默认的 CRA 设置(或者您已自定义配置它),我们可以使用以下命令运行测试:



npm run test


Enter fullscreen mode Exit fullscreen mode

并得到我们的结果:

测试结果

为应用程序添加暗黑模式

下面我只是导入并添加了<DarkMode />运行Create React App时创建的默认 App 模板。

src/App.tsx



import React from "react";
import logo from "./logo.svg";
import "./App.css";
import DarkMode from "./DarkMode";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <DarkMode />
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;


Enter fullscreen mode Exit fullscreen mode

最后,我们需要更新 CRA 设置中包含的默认 CSS,否则某些颜色/背景颜色值将覆盖我们的主题变量。

App.css以下示例是颜色值被注释掉的默认版本。如果您愿意,可以将其完全删除。

src/App.css



.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  /* background-color: #282c34; */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  /* color: white; */
}

.App-link {
  /* color: #61dafb; */
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}


Enter fullscreen mode Exit fullscreen mode

如果您遵循所有步骤,您将获得一个具有您自己的自定义<DarkMode />组件的功能强大的应用程序。

光明与黑暗的例子

设置首选配色方案

我们提到过,该应用程序支持用户浏览器配置首选配色方案,但实际上我们并没有解释如何设置该值。

不幸的是,浏览器并不容易做到这一点,但可以在 Chrome 或 Firefox 中按照以下步骤实现:

火狐

  • about:config在导航栏中输入
  • 如果不存在,则创建一个名为的值ui.systemUsesDarkTheme并将其设置为Number
  • 将数字设置为 1dark或 0light

铬合金

  • 打开开发者工具(F12)
  • ...单击工具右上角的省略号图标
  • 点击更多工具->渲染
  • 在“模拟 CSS 媒体”下选择“prefers-color-scheme: dark”

Chrome 有点棘手,所以这里有一个屏幕截图,显示了在哪里可以找到它:

偏好 Chrome 配色方案

总结

希望你喜欢本教程并从中有所收获!你可能已经注意到,虽然本教程是从React 的角度编写的,但我们使用的几乎所有代码都可以独立于 React 运行。

下次使用原生 HTML/CSS,甚至其他框架时,不妨试试这个!你会发现,只需稍加修改,这段代码就可以在任何地方重复使用。

请查看我的其他学习教程。如果您觉得其中有任何内容有用,请随时发表评论或提出问题并与他人分享:


想要了解更多类似教程,请在 Twitter 上关注我@eagleson_alex

文章来源:https://dev.to/alexeagleson/how-to-create-a-dark-mode-component-in-react-3ibg
PREV
如何创建和发布 React 组件库
NEXT
如何将 React 应用连接到 Notion 数据库