使用 styled-components 在 React 应用程序中实现暗模式

已发表: 2022-03-10
快速总结↬ Light 模式是大多数网络和移动应用程序的惯例。 然而,在现代开发中,我们已经看到在深色背景上显示浅色文本和界面元素的深色模式如何迅速成为用户偏好。 在本文中,我们将学习如何在一个简单的网页上的 React 应用程序中有效地实现暗模式,使用 styled-components 库并利用一些 React 功能,如钩子。 我们还将讨论暗模式的优缺点以及为什么应该采用它。

最常要求的软件功能之一是黑暗模式(或夜间模式,正如其他人所说的那样)。 我们在每天使用的应用程序中看到暗模式。 从移动设备到网络应用程序,暗模式对于想要照顾用户眼睛的公司来说已经变得至关重要。

深色模式是一项补充功能,在 UI 中主要显示深色表面。 大多数大公司(如 YouTube、Twitter 和 Netflix)都在其移动和 Web 应用程序中采用了暗模式。

虽然我们不会深入研究 React 和样式组件,但了解 React、CSS 和样式组件的基本知识会派上用场。 本教程将使那些希望通过迎合喜欢暗模式的人来增强其 Web 应用程序的人受益。

StackOverflow 在 Twitter 上宣布暗模式
StackOverflow 在 Twitter 上宣布暗模式(大预览)

在撰写本文的前几天,StackOverflow 宣布发布暗模式,让用户有机会在两种模式之间切换。

深色模式可减轻眼睛疲劳,并在您长时间使用电脑或手机工作时有所帮助。

什么是暗模式?

深色模式是任何界面的配色方案,它在深色背景上显示浅色文本和界面元素,这使得屏幕更容易看手机、平板电脑和电脑。 深色模式可减少屏幕发出的光,同时保持可读性所需的最小色彩对比度。

跳跃后更多! 继续往下看↓

为什么要关心暗模式?

暗模式通过减少眼睛疲劳、将屏幕调整到当前光线条件以及在夜间或黑暗环境中提供易用性来增强视觉人体工程学。

在我们的应用程序中实现暗模式之前,让我们看看它的好处。

节电

Web 和移动应用程序中的暗模式可以延长设备的电池寿命。 谷歌已经确认 OLED 屏幕上的暗模式对电池寿命有很大帮助。

例如,在 50% 的亮度下,YouTube 应用中的暗模式比纯白色背景节省了大约 15% 的屏幕能量。 在 100% 的屏幕亮度下,深色界面可节省高达 60% 的屏幕能耗。

黑暗模式很漂亮

深色模式很漂亮,可以显着提升屏幕的吸引力。

虽然大多数产品都采用类似的平淡白色外观,但深色模式提供了一些不同的东西,感觉神秘而新颖。

它还提供了以全新方式呈现图形内容(例如仪表板、图片和照片)的绝佳机会。

Twitter 深色与浅色模式
Twitter 的深色模式优于浅色模式(大预览)

既然您知道为什么应该在下一个 Web 应用程序中实现暗模式,那么让我们深入了解 styled-components,这是本教程的定义资源。

深色模式是在深色背景上显示浅色文本和界面元素的任何界面的配色方案,这使得在手机、平板电脑和计算机上查看起来更容易一些。

什么是样式化组件?

在整篇文章中,我们将经常使用 styled-components 库。 设计现代 Web 应用程序的方式一直有很多。 在文档级别有传统的样式设置方法,包括创建index.css文件并将其链接到 HTML 或 HTML 文件中的样式。

自从引入 CSS-in-JS 以来,Web 应用程序的样式最近发生了很大变化。

CSS-in-JS 是指使用 JavaScript 编写 CSS 的模式。 它利用标记的模板文字来设置 JavaScript 文件中的组件样式。

要了解有关 CSS-in-JS 的更多信息,请查看 Anna Monus 关于该主题的文章。

styled-components 是一个 CSS-in-JS 库,可让您使用您喜欢的 CSS 的所有功能,包括媒体查询、伪选择器和嵌套。

为什么选择样式组件?

创建 styled-components 的原因如下:

  • 没有类名地狱
    styled-components 无需为元素寻找类名,而是为您的样式生成唯一的类名。 您永远不必担心拼写错误或使用没有意义的类名。
  • 使用道具
    styled-components 允许我们使用 React 中常用的props参数来扩展样式属性——因此,通过应用程序的状态动态地影响组件的感觉。
  • 支持 Sass 语法
    使用 styled-components 可以开箱即用地编写 Sass 语法,而无需设置任何预处理器或额外的构建工具。 在您的样式定义中,您可以使用&字符来定位当前组件、使用伪选择器并尝试嵌套。
  • 主题化
    styled-components 通过导出ThemeProvider包装器组件具有完整的主题支持。 该组件通过 Context API 为自身内部的所有 React 组件提供了一个主题。 在渲染树中,所有样式组件都可以访问提供的主题,即使它们是多层次的。 随着我们继续本教程,我们将深入研究样式化组件的主题功能。

要了解样式组件的更多优点,请查看 Kris Guzman 的文章。

实施暗模式

在本文中,我们将在一个类似 YouTube 的简单网页上实现暗模式。

要继续进行,请确保从starter分支克隆原始存储库。

配置

让我们在package.json文件中安装所有依赖项。 从终端运行以下命令:

 npm install

安装成功后,运行npm start 。 这是未在其上实施暗模式的网页的外观。

要使用的网页,没有暗模式
要使用的网页,没有暗模式。 (大预览)

要安装styled-components ,请在终端中运行npm install styled-components

执行

要实现暗模式,我们需要创建四个不同的组件。

  • Theme
    这包含我们的浅色和深色主题的颜色属性。
  • GlobalStyles
    这包含整个文档的全局样式。
  • Toggler
    这包含切换功能的按钮元素。
  • useDarkMode
    这个自定义钩子处理主题更改背后的逻辑以及我们的主题在 localStorage 中的持久性。

主题组件

src文件夹中,您将在components文件夹中看到组件。 创建一个Themes.js文件,并向其中添加以下代码。

 export const lightTheme = { body: '#FFF', text: '#363537', toggleBorder: '#FFF', background: '#363537', } export const darkTheme = { body: '#363537', text: '#FAFAFA', toggleBorder: '#6B8096', background: '#999', }

在这里,我们定义并导出了具有不同颜色变量的lightThemedarkTheme对象。 随意试验和自定义变量以适合您。

全局样式组件

在您的components文件夹中,创建一个globalStyles.js文件,并添加以下代码:

 import { createGlobalStyle} from "styled-components" export const GlobalStyles = createGlobalStyle` body { background: ${({ theme }) => theme.body}; color: ${({ theme }) => theme.text}; font-family: Tahoma, Helvetica, Arial, Roboto, sans-serif; transition: all 0.50s linear; } `

我们从 styled-components 导入了createGlobalStylecreateGlobalStyle方法替换了 styled-components 版本 3 中现已弃用的 injectGlobal 方法。该方法生成一个 React 组件,当添加到您的组件树中时,它将全局样式注入到文档中,在我们的例子中是App.js

我们定义了一个GlobalStyle组件并将backgroundcolor属性分配给来自主题对象的值。 因此,每次我们切换切换时,值都会根据我们传递给ThemeProvider的深色主题或浅色主题对象而变化(稍后将在我们继续进行时创建)。

0.50s的过渡属性使这种变化发生得更平滑一些,所以当我们来回切换时,我们可以看到变化发生。

创建主题切换功能

要实现主题切换功能,我们只需要添加几行代码。 在App.js文件中,添加以下代码(注意突出显示的代码是您应该添加的):

 import React, { useState, useEffect } from "react";
import {ThemeProvider} from "styled-components"; import { GlobalStyles } from "./components/Globalstyle"; import { lightTheme, darkTheme } from "./components/Themes"
import "./App.css"; import dummyData from "./data"; import CardList from "./components/CardList"; const App = () => { const [videos, setVideos] = useState([]);
 const [theme, setTheme] = useState('light'); const themeToggler = () => { theme === 'light' ? setTheme('dark') : setTheme('light') }
 useEffect(() => { const timer = setTimeout(() => { setVideos(dummyData); }, 1000); return () => clearTimeout(timer); }, []); return (
 <ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}> <> <GlobalStyles/>
 <div className="App">
 <button onClick={themeToggler}>Switch Theme</button>
 { videos.map((list, index) => { return ( <section key={index}> <h2 className="section-title">{list.section}</h2> <CardList list={list} /> <hr /> </section> ); })} </div>
 </> </ThemeProvider>
); }; export default App;

突出显示的代码是新添加到App.js的代码。 我们从styled-components导入了ThemeProviderThemeProvider是 styled-components 库中提供主题支持的辅助组件。 这个帮助组件通过 Context API 将一个主题注入到它自身下面的所有 React 组件中。

在渲染树中,所有样式组件都可以访问提供的主题,即使它们是多层次的。 查看“主题”部分。

接下来,我们从./components/Globalstyle导入GlobalStyle包装器。 最后,从顶部开始,我们从./components/Themes导入lightThemedarkTheme对象。

为了让我们创建一个切换方法,我们需要一个状态来保存我们主题的初始颜色值。 因此,我们创建了一个theme状态,并将初始状态设置为light ,使用useState钩子。

现在,对于切换功能。

themeToggler方法使用三元运算符来检查theme的状态,并根据条件的值切换暗或亮。

ThemeProvider是一个样式化组件帮助器组件,它将所有内容包装在return语句中,并在其下方注入任何组件。 请记住,我们的GlobalStyles全局样式注入到我们的组件中; 因此,它在ThemeProvider包装器组件中被调用。

最后,我们创建了一个带有onClick事件的按钮,该事件将我们的themeToggler方法分配给它。

让我们看看到目前为止的结果。

无需持久性即可实现暗模式。
无持久性实现的暗模式(大预览)

我们的App.js文件需要重构; 它的很多代码不是 DRY。 (DRY 代表“不要重复自己”,这是旨在减少重复的软件开发的基本原则。)所有的逻辑似乎都在App.js中; 为了清楚起见,将我们的逻辑分开是一种很好的做法。 因此,我们将创建一个处理切换功能的组件。

切换组件

仍然在components文件夹中,创建一个Toggler.js文件,并向其中添加以下代码:

 import React from 'react' import { func, string } from 'prop-types'; import styled from "styled-components" const Button = styled.button` background: ${({ theme }) => theme.background}; border: 2px solid ${({ theme }) => theme.toggleBorder}; color: ${({ theme }) => theme.text}; border-radius: 30px; cursor: pointer; font-size:0.8rem; padding: 0.6rem; } \`; const Toggle = ({theme, toggleTheme }) => { return ( <Button onClick={toggleTheme} > Switch Theme </Button> ); }; Toggle.propTypes = { theme: string.isRequired, toggleTheme: func.isRequired, } export default Toggle;

为了保持整洁,我们使用 styled-components 中的styled函数在Toggle组件中设置了切换按钮的样式。

这纯粹是为了演示; 您可以按照您认为合适的方式设置按钮样式。

Toggle组件中,我们传递了两个 props:

  • theme提供当前主题(浅色或深色);
  • toggleTheme功能将用于在主题之间切换。

接下来,我们返回Button组件并为onClick事件分配一个toggleTheme函数。

最后,我们使用propTypes来定义我们的类型,确保我们的theme是一个stringisRequired ,而我们的toggleThemefuncisRequired

使用自定义 Hooks ( useDarkMode )

在构建应用程序时,可扩展性是最重要的,这意味着我们的业务逻辑必须是可重用的,这样我们才能在很多地方甚至不同的项目中使用它。

这就是为什么将我们的切换功能移到单独的组件中会很棒的原因。 为此,我们将创建自己的自定义钩子。

让我们在components文件夹中创建一个名为useDarkMode.js的新文件,并将我们的逻辑移至该文件,并进行一些调整。 将以下代码添加到文件中:

 import { useEffect, useState } from 'react'; export const useDarkMode = () => { const [theme, setTheme] = useState('light'); const setMode = mode => { window.localStorage.setItem('theme', mode) setTheme(mode) }; const themeToggler = () => { theme === 'light' ? setMode('dark') : setMode('light') }; useEffect(() => { const localTheme = window.localStorage.getItem('theme'); localTheme && setTheme(localTheme) }, []); return [theme, themeToggler] };

我们在这里添加了一些东西。

  • setMode
    我们使用localStorage在浏览器中的会话之间进行持久化。 因此,如果用户选择了深色或浅色主题,这就是他们下次访问应用程序或重新加载页面时将获得的内容。 因此,此函数设置我们的状态并将theme传递给localStorage
  • themeToggler
    此函数使用三元运算符来检查主题的状态,并根据条件的真实性切换暗或亮。
  • useEffect
    我们已经实现了useEffect挂钩来检查组件安装。 如果用户之前选择了一个主题,我们会将它传递给我们的setTheme函数。 最后,我们将返回我们的theme ,其中包含选择的theme和用于在模式之间切换的themeToggler函数。

我想你会同意我们的暗模式组件看起来很时尚。

让我们前往App.js进行最后的润色。

 import React, { useState, useEffect } from "react"; import {ThemeProvider} from "styled-components";
import {useDarkMode} from "./components/useDarkMode"
import { GlobalStyles } from "./components/Globalstyle"; import { lightTheme, darkTheme } from "./components/Themes" import Toggle from "./components/Toggler" import "./App.css"; import dummyData from "./data"; import CardList from "./components/CardList"; const App = () => { const [videos, setVideos] = useState([]);
 const [theme, themeToggler] = useDarkMode(); const themeMode = theme === 'light' ? lightTheme : darkTheme;
useEffect(() => { const timer = setTimeout(() => { setVideos(dummyData); }, 1000); return () => clearTimeout(timer); }, []); return (
 <ThemeProvider theme={themeMode}>
 <> <GlobalStyles/> <div className="App">
 <Toggle theme={theme} toggleTheme={themeToggler} />
 { videos.map((list, index) => { return ( <section key={index}> <h2 className="section-title">{list.section}</h2> <CardList list={list} /> <hr /> </section> ); })} </div> </> </ThemeProvider> ); }; export default App;

突出显示的代码是新添加到App.js

首先,我们导入自定义钩子,解构themethemeToggler ,并使用useDarkMode函数进行设置。

请注意, useDarkMode方法替换了我们最初在App.js中的theme状态。

我们声明了一个themeMode变量,它根据当时theme模式的条件呈现浅色或深色主题。

现在,我们的ThemeProvider包装器组件被分配了我们刚刚创建的themeMode变量给theme属性。

最后,代替常规按钮,我们传入Toggle组件。

请记住,在我们的Toggle组件中,我们定义了一个按钮并设置了样式,并将themetoggleTheme作为 props 传递给它们。 因此,我们所要做的就是将这些 props 适当地传递给Toggle组件,该组件将充当我们在App.js中的按钮。

是的! 我们的暗模式已设置,并且在刷新页面或在新选项卡中访问时不会改变颜色。

让我们看看实际的结果:

已实现暗模式,但在浏览器重新加载时按钮颜色出现故障。
已实现暗模式,但在浏览器重新加载时按钮颜色出现故障。 (大预览)

几乎所有东西都运行良好,但我们可以做一件小事来让我们的体验更加精彩。 切换到深色主题,然后重新加载页面。 您是否看到按钮中的蓝色在灰色之前加载了片刻? 发生这种情况是因为我们的useState钩子最初启动了light主题。 之后, useEffect运行,检查localStorage ,然后才将theme设置为dark 。 让我们跳到我们的自定义钩子useDarkMode.js并添加一些代码:

 import { useEffect, useState } from 'react'; export const useDarkMode = () => { const [theme, setTheme] = useState('light');
 const [mountedComponent, setMountedComponent] = useState(false)
 const setMode = mode => { window.localStorage.setItem('theme', mode) setTheme(mode) }; const themeToggler = () => { theme === 'light' ? setMode('dark') : setMode('light') }; useEffect(() => { const localTheme = window.localStorage.getItem('theme'); localTheme ? setTheme(localTheme) : setMode('light')
 setMountedComponent(true)
 }, []); return [theme, themeToggler, }, []); return [theme, themeToggler, mountedComponent ]
};

突出显示的代码是唯一添加到useDarkMode.js的代码。 我们创建了另一个名为mountedComponent的状态,并使用useState挂钩将默认值设置为false 。 接下来,在useEffect钩子中,我们使用setMountedComponentmountedComponent状态设置为true 。 最后,在return数组中,我们包含了mountedComponent状态。

最后,让我们在App.js中添加一些代码以使其正常工作。

 import React, { useState, useEffect } from "react"; import {ThemeProvider} from "styled-components"; import {useDarkMode} from "./components/useDarkMode" import { GlobalStyles } from "./components/Globalstyle"; import { lightTheme, darkTheme } from "./components/Themes" import Toggle from "./components/Toggler" import "./App.css"; import dummyData from "./data"; import CardList from "./components/CardList"; const App = () => { const [videos, setVideos] = useState([]);
 const [theme, themeToggler, mountedComponent] = useDarkMode();
 const themeMode = theme === 'light' ? lightTheme : darkTheme; useEffect(() => { const timer = setTimeout(() => { setVideos(dummyData); }, 1000); return () => clearTimeout(timer); }, []);
 if(!mountedComponent) return <div/>
 return ( <ThemeProvider theme={themeMode}> <> <GlobalStyles/> <div className="App"> <Toggle theme={theme} toggleTheme={themeToggler} /> { videos.map((list, index) => { return ( <section key={index}> <h2 className="section-title">{list.section}</h2> <CardList list={list} /> <hr /> </section> ); })} </div> </> </ThemeProvider> ); }; export default App;

我们在useDarkMode钩子中添加了mountedComponent状态作为道具,并且我们检查了我们的组件是否已安装,因为这是在useEffect钩子中发生的。 如果它还没有发生,那么我们将渲染一个空的div

让我们看看我们的暗模式网页的结果。

深色模式的最终结果
深色模式的最终结果(大预览)

现在,您会注意到在暗模式下,当页面重新加载时,按钮的颜色不会改变。

结论

暗模式越来越成为用户偏好,在样式化组件中使用ThemeProvider主题包装器时,在 React Web 应用程序中实现它要容易得多。 在实现暗模式时继续尝试样式化组件; 您可以添加图标而不是按钮。

请在下面的评论部分分享您对 styled-components 中主题化功能的反馈和体验。 我很想看看你想出了什么!

本文的支持存储库可在 GitHub 上找到。 另外,请在 CodeSandbox 上查看。

参考

  • “文档”,样式化组件
  • “使用样式化组件创建应用程序的暗模式”,Tom Nolan,Medium