Реализация темного режима в приложениях React с использованием стилизованных компонентов
Опубликовано: 2022-03-10Одной из наиболее часто запрашиваемых программных функций является темный режим (или ночной режим, как его называют другие). Мы видим темный режим в приложениях, которые используем каждый день. От мобильных до веб-приложений темный режим стал жизненно важным для компаний, которые хотят заботиться о глазах своих пользователей.
Темный режим — это дополнительная функция, которая отображает в пользовательском интерфейсе в основном темные поверхности. Большинство крупных компаний (например, YouTube, Twitter и Netflix) внедрили темный режим в свои мобильные и веб-приложения.
Хотя мы не будем углубляться в React и стилизованные компоненты, вам пригодятся базовые знания React, CSS и стилизованных компонентов. Это руководство будет полезно тем, кто хочет улучшить свои веб-приложения, ориентируясь на тех, кто любит темный режим.

За несколько дней до написания этой статьи StackOverflow объявил о выпуске темного режима, который дает пользователям возможность переключаться между двумя режимами.
Темный режим снижает нагрузку на глаза и помогает при длительной работе за компьютером или мобильным телефоном.
Что такое темный режим?
Темный режим — это цветовая схема любого интерфейса, которая отображает светлый текст и элементы интерфейса на темном фоне, что делает экран немного удобнее для просмотра на мобильных телефонах, планшетах и компьютерах. Темный режим уменьшает свет, излучаемый экраном, сохраняя при этом минимальные соотношения цветов и контрастности, необходимые для удобочитаемости.
Почему вы должны заботиться о темном режиме?
Темный режим улучшает визуальную эргономику, снижая нагрузку на глаза, настраивая экран в соответствии с текущими условиями освещения и обеспечивая простоту использования ночью или в темноте.
Прежде чем внедрять темный режим в наше приложение, давайте посмотрим на его преимущества.
Экономия батареи
Темный режим в веб-приложениях и мобильных приложениях может продлить срок службы аккумулятора устройства. Google подтвердил, что темный режим на OLED-экранах значительно увеличил время автономной работы.
Например, при яркости 50 % темный режим в приложении YouTube экономит примерно на 15 % больше энергии экрана, чем плоский белый фон. При 100% яркости экрана темный интерфейс экономит колоссальные 60% энергии экрана.
Темный режим прекрасен
Темный режим прекрасен и может значительно повысить привлекательность экрана.
В то время как большинство продуктов имеют такой же мягкий белый вид, темный режим предлагает что-то другое, что кажется загадочным и новым.
Он также предоставляет отличные возможности для представления графического содержимого, такого как информационные панели, изображения и фотографии, по-новому.

Теперь, когда вы знаете, почему вы должны реализовать темный режим в своем следующем веб-приложении, давайте углубимся в стилизованные компоненты, которые являются определяющим ресурсом этого руководства.
Темный режим — это цветовая схема любого интерфейса, при которой светлый текст и элементы интерфейса отображаются на темном фоне, что немного облегчает просмотр на мобильных телефонах, планшетах и компьютерах.
“
Что такое стилизованные компоненты?
В этой статье мы будем очень часто использовать библиотеку styled-components. Всегда было много способов стилизовать современное веб-приложение. Существует традиционный метод стилизации на уровне документа, который включает создание файла index.css
и связывание его с HTML или стилем внутри файла HTML.
В последнее время многое изменилось в стилях веб-приложений с момента появления CSS-in-JS.
CSS-in-JS относится к шаблону, в котором CSS составлен с использованием JavaScript. Он использует помеченные литералы шаблонов для стилизации компонентов в файле JavaScript.
Чтобы узнать больше о CSS-in-JS, ознакомьтесь со статьей Анны Монус на эту тему.
styled-components — это библиотека CSS-in-JS, позволяющая использовать все функции CSS, которые вам нравятся, включая медиазапросы, псевдоселекторы и вложенность.
Почему стилизованные компоненты?
styled-components был создан по следующим причинам:
- Ад без имени класса
Вместо того, чтобы ломать голову над поиском имени класса для элемента, styled-components генерирует уникальные имена классов для ваших стилей. Вам никогда не придется беспокоиться об ошибках в написании или использовании имен классов, которые не имеют смысла. - Использование реквизита
styled-components позволяют нам расширять свойства стиля с помощью параметраprops
, обычно используемого в React, — таким образом, динамически влияя на ощущение компонента через состояние приложения. - Поддерживает синтаксис Sass.
Написание синтаксиса Sass из коробки без необходимости установки каких-либо препроцессоров или дополнительных инструментов сборки возможно с помощью styled-components. В определениях стилей вы можете использовать символ&
для выбора текущего компонента, использовать псевдоселекторы и экспериментировать с вложенностью. - Тематика
styled-components имеют полную поддержку тем за счет экспорта компонента-оболочкиThemeProvider
. Этот компонент предоставляет тему для всех компонентов React внутри себя через Context API. В дереве рендеринга все стилизованные компоненты будут иметь доступ к предоставленной теме, даже если они имеют несколько уровней глубины. По мере того, как мы продолжим работу с этим руководством, мы углубимся в тематические функции styled-components.
Чтобы узнать больше о преимуществах styled-components, ознакомьтесь со статьей Криса Гузмана.
Реализация темного режима
В этой статье мы собираемся реализовать темный режим на простой веб-странице, похожей на 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', }
Здесь мы определили и экспортировали lightTheme
и darkTheme
с различными цветовыми переменными. Не стесняйтесь экспериментировать и настраивать переменные в соответствии с вашими потребностями.
Компонент globalStyles
Оставаясь в папке 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; } `
Мы импортировали createGlobalStyle
из styled-components. Метод createGlobalStyle
заменяет уже устаревший метод injectGlobal из styled-components версии 3. Этот метод генерирует компонент React, который при добавлении в ваше дерево компонентов будет внедрять в документ глобальные стили, в нашем случае App.js
Мы определили компонент GlobalStyle
и присвоили свойства background
и color
значениям из объекта темы. Таким образом, каждый раз, когда мы переключаем переключатель, значения будут меняться в зависимости от объектов темной темы или светлой темы, которые мы передаем в 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
Мы импортировали ThemeProvider
из styled-components
. ThemeProvider
— это вспомогательный компонент в библиотеке styled-components, обеспечивающий поддержку тем. Этот вспомогательный компонент внедряет тему во все компоненты React ниже себя через Context API.
В дереве рендеринга все стилизованные компоненты будут иметь доступ к предоставленной теме, даже если они имеют несколько уровней глубины. Ознакомьтесь с разделом «Тематика».
Затем мы импортируем оболочку GlobalStyle
из ./components/Globalstyle
. Наконец, сверху мы импортируем объекты lightTheme
и darkTheme
из ./components/Themes
.

Чтобы мы могли создать метод переключения, нам нужно состояние, которое содержит начальное значение цвета нашей темы. Итак, мы создаем состояние theme
и устанавливаем начальное состояние на light
с помощью хука useState
.
Теперь о функции переключения.
Метод themeToggler
использует тернарный оператор для проверки состояния theme
и переключает темную или светлую тему в зависимости от значения условия.
ThemeProvider
, вспомогательный компонент styled-components, оборачивает все в оператор 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;
Чтобы все было аккуратно, мы стилизовали нашу кнопку-переключатель в компоненте Toggle
, используя styled
функцию из styled-components.
Это чисто для презентации; вы можете стилизовать кнопку по своему усмотрению.
Внутри компонента Toggle
мы передаем два реквизита:
-
theme
обеспечивает текущую тему (светлая или темная); - функция
toggleTheme
будет использоваться для переключения между темами.
Затем мы возвращаем компонент Button
и назначаем функцию toggleTheme
onClick
Наконец, мы используем propTypes
для определения наших типов, гарантируя, что наша theme
является string
и isRequired
, а наша toggleTheme
является func
и isRequired
.
Использование пользовательских хуков ( useDarkMode
)
При создании приложения масштабируемость имеет первостепенное значение, а это означает, что наша бизнес-логика должна быть многоразовой, чтобы мы могли использовать ее во многих местах и даже в разных проектах.
Вот почему было бы здорово перенести нашу функцию переключения в отдельный компонент. Для этого мы создадим собственный кастомный хук.
Давайте создадим новый файл с именем useDarkMode.js
в папке components
и переместим нашу логику в этот файл с некоторыми изменениями. Добавьте в файл следующий код:
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
Во-первых, мы импортируем наш пользовательский хук, деструктурируем theme
и реквизиты themeToggler
и устанавливаем их с помощью функции useDarkMode
.
Обратите внимание, что метод useDarkMode
заменяет наше состояние theme
, которое изначально было в App.js
Мы объявляем переменную themeMode
, которая отображает либо светлую, либо темную тему в зависимости от состояния режима theme
в данный момент.
Теперь наш компонент-оболочка ThemeProvider
назначает нашу только что созданную переменную themeMode
theme
.
И, наконец, вместо обычной кнопки мы передаем компонент Toggle
.
Помните, что в нашем компоненте Toggle
мы определили и стилизовали кнопку и передали им theme
и toggleTheme
в качестве свойств. Итак, все, что нам нужно сделать, это передать эти свойства компоненту 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
и установили значение по умолчанию в false
с помощью хука useState
. Затем внутри хука useEffect
мы устанавливаем состояние mountedComponent
в true
, используя setMountedComponent
. Наконец, в 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;
Мы добавили состояние mountedComponent
в качестве реквизита в хук useDarkMode
и проверили, смонтировался ли наш компонент, потому что именно это происходит в useEffect
. Если этого еще не произошло, то будем рендерить пустой div
.
Давайте посмотрим на результат нашей веб-страницы в темном режиме.

Теперь вы заметите, что в темном режиме при перезагрузке страницы цвет кнопки не меняется.
Заключение
Темный режим все чаще становится предпочтением пользователей, и реализовать его в веб-приложении React намного проще, если использовать оболочку темы ThemeProvider
в styled-components. Продолжайте экспериментировать со стилизованными компонентами при реализации темного режима; вы можете добавить значки вместо кнопки.
Пожалуйста, поделитесь своими отзывами и опытом использования функции тем в styled-components в разделе комментариев ниже. Я хотел бы увидеть, что вы придумали!
Вспомогательный репозиторий для этой статьи доступен на GitHub. Кроме того, проверьте это на CodeSandbox.
использованная литература
- «Документация», styled-components
- «Создайте темный режим вашего приложения с помощью стилизованных компонентов», Том Нолан, Medium