Реализация темного режима в приложениях React с использованием стилизованных компонентов

Опубликовано: 2022-03-10
Краткий обзор ↬ Облегченный режим используется в большинстве веб-приложений и мобильных приложений. Однако в современных разработках мы увидели, как темный режим, в котором светлый текст и элементы интерфейса отображаются на темном фоне, быстро становится предпочтительным для пользователей. В этой статье мы узнаем, как эффективно реализовать темный режим в приложении React на простой веб-странице, используя библиотеку styled-components и некоторые функции React, такие как хуки. Мы также обсудим плюсы и минусы темного режима и почему его следует использовать.

Одной из наиболее часто запрашиваемых программных функций является темный режим (или ночной режим, как его называют другие). Мы видим темный режим в приложениях, которые используем каждый день. От мобильных до веб-приложений темный режим стал жизненно важным для компаний, которые хотят заботиться о глазах своих пользователей.

Темный режим — это дополнительная функция, которая отображает в пользовательском интерфейсе в основном темные поверхности. Большинство крупных компаний (например, YouTube, Twitter и Netflix) внедрили темный режим в свои мобильные и веб-приложения.

Хотя мы не будем углубляться в React и стилизованные компоненты, вам пригодятся базовые знания React, CSS и стилизованных компонентов. Это руководство будет полезно тем, кто хочет улучшить свои веб-приложения, ориентируясь на тех, кто любит темный режим.

StackOverflow объявляет о темном режиме в Twitter
StackOverflow объявляет о темном режиме в Twitter (большой предварительный просмотр)

За несколько дней до написания этой статьи StackOverflow объявил о выпуске темного режима, который дает пользователям возможность переключаться между двумя режимами.

Темный режим снижает нагрузку на глаза и помогает при длительной работе за компьютером или мобильным телефоном.

Что такое темный режим?

Темный режим — это цветовая схема любого интерфейса, которая отображает светлый текст и элементы интерфейса на темном фоне, что делает экран немного удобнее для просмотра на мобильных телефонах, планшетах и ​​компьютерах. Темный режим уменьшает свет, излучаемый экраном, сохраняя при этом минимальные соотношения цветов и контрастности, необходимые для удобочитаемости.

Еще после прыжка! Продолжить чтение ниже ↓

Почему вы должны заботиться о темном режиме?

Темный режим улучшает визуальную эргономику, снижая нагрузку на глаза, настраивая экран в соответствии с текущими условиями освещения и обеспечивая простоту использования ночью или в темноте.

Прежде чем внедрять темный режим в наше приложение, давайте посмотрим на его преимущества.

Экономия батареи

Темный режим в веб-приложениях и мобильных приложениях может продлить срок службы аккумулятора устройства. Google подтвердил, что темный режим на OLED-экранах значительно увеличил время автономной работы.

Например, при яркости 50 % темный режим в приложении YouTube экономит примерно на 15 % больше энергии экрана, чем плоский белый фон. При 100% яркости экрана темный интерфейс экономит колоссальные 60% энергии экрана.

Темный режим прекрасен

Темный режим прекрасен и может значительно повысить привлекательность экрана.

В то время как большинство продуктов имеют такой же мягкий белый вид, темный режим предлагает что-то другое, что кажется загадочным и новым.

Он также предоставляет отличные возможности для представления графического содержимого, такого как информационные панели, изображения и фотографии, по-новому.

Темный и светлый режим Twitter
Преимущество тёмного режима Твиттера над светлым (большой превью)

Теперь, когда вы знаете, почему вы должны реализовать темный режим в своем следующем веб-приложении, давайте углубимся в стилизованные компоненты, которые являются определяющим ресурсом этого руководства.

Темный режим — это цветовая схема любого интерфейса, при которой светлый текст и элементы интерфейса отображаются на темном фоне, что немного облегчает просмотр на мобильных телефонах, планшетах и ​​компьютерах.

Что такое стилизованные компоненты?

В этой статье мы будем очень часто использовать библиотеку 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