В современном мире ночной режим, который отображает светлые элементы интерфейса на тёмном фоне, быстро становится фаворитом у большинства пользователей. В этой статье вы узнаете, как эффективно реализовать ночной режим в React-приложении, используя библиотеку styled-components и некоторые функции React. Кроме того, мы обсудим плюсы и минусы ночного режима, а также причины, по которым он должен быть в каждом приложении.
Одной из наиболее часто запрашиваемых программных функций является ночной режим (или тёмный режим). Мы видим его почти во всех приложениях, которые привыкли использовать каждый день: начиная с мобильных и заканчивая веб-версиями. Ночной режим стал жизненно важным для компаний, которые хотят позаботиться о глазах своих пользователей.
Тёмный режим – это дополнительная функция, которая преобразует светлые элементы интерфейса в тёмные. Большинство крупных компаний (таких как YouTube, Twitter и Netflix) добавили ночной режим в свои приложения.
Несмотря на то, что мы не будем особо углубляться в сам React, базовые знания о нём, CSS и styled-components вам всё же понадобятся. Этот урок будет полезен для тех, кто хочет улучшить своё веб-приложение, ориентируясь на любителей ночного режима.

Тёмный режим снижает нагрузку на глаза, если вы долго работаете за компьютером или мобильным телефоном.
Что такое тёмный режим?
Темный режим – это цветовая схема любого интерфейса, который отображает светлый текст и элементы интерфейса на тёмном фоне, что уменьшает нагрузку на глаза при просмотре на мобильных телефонах, планшетах и компьютерах. Этот режим уменьшает свет, передаваемый экраном, сохраняя при этом минимальные цветовые контрасты, необходимые для удобочитаемости.
Почему вашему приложению нужен тёмный режим?
Ночной режим улучшает визуальную эргономику, уменьшая нагрузку на глаза, подстраивая экран под текущие условия освещения и обеспечивая удобство использования ночью или в темноте.
Прежде чем начать реализовывать тёмный режим в нашем приложении, давайте определимся с его преимуществами.
ЭКОНОМИЯ ЗАРЯДКИ
Тёмный режим в веб- и мобильных приложениях может продлить срок службы аккумулятора устройства. Google подтвердил, что дарк мод на OLED-экранах очень сильно продлил срок службы батареи.
Например, при яркости в 50% ночной режим в приложении YouTube экономит примерно на 15% больше энергии, чем обычный белый фон. При яркости экрана в 100% тёмный интерфейс экономит колоссальные 60% энергии.
ЭТО ПРОСТО КРАСИВО
Внешний вид тёмного режима является эстетичным, что значительно повышает привлекательность картинки.
Когда большинство продуктов имеют однотипную светлую палитру, ночной режим предлагает нечто другое – что-то таинственнее и новее.
К тому же он даёт прекрасные возможности для свежего взгляда на такой графический контент, как информационные панели, изображения и фотографии.

Теперь, когда вы знаете, почему вам необходим ночной режим в вашем веб-приложении, давайте углубимся в компоненты стилей, которые являются определяющим ресурсом этого руководства.
Что Такое Styled-Components?
В этой статье мы будем очень часто использовать библиотеку styled-components. Существует множество способов стилизации современного веб-приложения. Традиционный – метод стилей на уровне документов, при котором нужно или создать файл index.css и связать его с HTML или прописывать стили непосредственно внутри самого HTML-файла.
В стилях веб-приложений много чего изменилось с тех пор, как появился CSS-in-JS.
CSS-in-JS ссылается на паттерн, в котором CSS составлен с использованием JavaScript. Там используются теговые литералы для стилизации компонентов в файле JavaScript.
Styled-components – это библиотека CSS-in-JS, которая позволяет использовать все ваши любимые CSS-функции, включая медиа-запросы, псевдоселекторы и вложения.
Почему именно Styled-Components?
Библиотека Styled-components была выбрана по следующим причинам:
- Нет неразберихи с именами классов. Вместо того, чтобы ломать голову над новым именем класса, styled-components генерирует уникальное имя само! Вам больше не придётся беспокоиться об ошибках или бессмысленных именах классов.
- Использование пропсов. Styled-components позволяет расширять свойства стиля с помощью параметра props, обычно используемого в React, – таким образом, динамически влияя на ощущение компонента через состояние приложения.
- Поддержка синтаксиса Sass. Написание Sass-синтаксиса из коробки без необходимости установки каких-либо препроцессоров или дополнительных инструментов возможно с помощью styled-components. В ваших определениях стилей вы можете использовать символ & для указания на текущий компонент.
- Темы. Styled-components поддерживает темы, экспортируя компонент-оболочку
ThemeProvider
. Этот компонент присваивает тему всем компонентам React внутри себя через Context API. В дереве рендеринга все стилевые компоненты будут иметь доступ к предоставленной теме, даже если они имеют несколько уровней. В течении этого урока, мы будем углубляться в такого рода особенности styled-components.
Реализация Ночного Режима
В этой статье мы собираемся реализовать темный режим на простой YouTube-подобной веб-странице.
Чтобы продолжить, убедитесь, что вы клонировали исходный репозиторий.
СБОРКА
Давайте установим все зависимости в наш package.json
файл. В терминале выполните следующую команду:
npm install
После успешной установки пропишите npm start
. Вот как выглядит веб-страница без встроенного тёмного режима:

Чтобы установить styled-components, в терминале запустите:
npm install styled-components
РЕАЛИЗАЦИЯ
Для реализации ночного режима необходимо создать четыре разных компонента.
Theme
– Содержит свойства цветов светлой и тёмной тем.GlobalStyles
– Содержит глобальные стили всего документа.Toggler
– Содержит элемент кнопки переключения режимов.useDarkMode
– Пользовательский хук, который обрабатывает логику смены темы и её сохранение вlocalStorag
.
КОМПОНЕНТ ТЕМЫ
В папке 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
с отличающимися переменными цветами. Не стесняйтесь экспериментировать и настраивать их значения в соответствии с вашими предпочтениями.
КОМПОНЕНТ ГЛОБАЛЬНЫХ СТИЛЕЙ
Не покидая папку 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. Он генерирует 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 означает «don’t repeat yourself», основной принцип разработки ПО, направленный на уменьшение количества повторений.) Похоже, что вся логика находится в 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
, которая содержит выбранную тему и функцию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
заменяет состояние темы, которое изначально было в App.js
.
Мы объявляем переменную themeMode
, которая отображает либо светлую, либо тёмную тему, в зависимости от состояния режима theme
в данный момент.
Теперь нашему компоненту-оболочке ThemeProvider
назначена только что созданная переменная themeMode
для пропса theme
.
И, наконец, вместо обычной кнопки передаём компонент Toggle
.
Помните, что в нашем компоненте Toggle
мы определили и стилизовали кнопку, а так же передали им theme
и toggleTheme
в качестве пропсов. Итак, всё, что нам нужно сделать, это передать эти пропсы соответствующим образом компоненту Toggle
, который будет выполнять функцию нашей кнопки в App.js
.
Ура! Наша ночная тема установлена и умеет не менять цвет, когда страница обновляется или открывается в новой вкладке.
Давайте посмотрим на текущий результат в действии:

Почти всё работает хорошо, но есть одна маленькая вещь, сделав которую, переключение тем станет великолепным. Переключитесь на ночную тему и обновите страницу. Видите, что синий цвет в кнопке загружается чуть раньше, чем серый? Это происходит потому, что наш хук useState
изначально запускает тему light
. После этого запускается useEffect
, проверяет localStorage
и только потом устанавливает тёмную тему. Давайте перейдем к нашему пользовательскому хуку 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,mountedComponent]
};
Выделенный код – всё, что нужно было добавить в useDarkMode.js
. Мы создали другое состояние с именем mountComponent
и установили значение по умолчанию в false
, используя хук useState
. Далее в хуке useEffect
мы устанавливаем состояние mountComponent
в значение true
, используя setMountingComponent
. Наконец, в возвращаемый массив добавляем состояние mountComponent
.
А теперь давайте добавим немного кода в 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;
Мы добавили наше состояние mountComponent
в качестве опоры для хука useDarkMode
и проверили, установлен ли компонент, что и происходит в хуке useEffect
. Если этого не произошло, то мы сделаем div
пустым.
Давайте посмотрим на нашу тёмную веб-страницу.

Теперь, когда страница обновляется, цвет кнопки остаётся неизменным.
Заключение
Ночная тема всё чаще становится фаворитом среди пользователей, а реализовать её в веб-приложении React становится значительно проще, если использовать тематическую оболочку ThemeProvider
в styled-components. Двигайтесь вперёд и экспериментируйте со стилевыми компонентами при реализации тёмной темы. Кстати, вместо кнопки можно сделать значки.
Читайте так же: Как создать привлекательный лендинг