Implementacja trybu ciemnego w aplikacjach React przy użyciu styled-components

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ Tryb jasny to konwencja w większości aplikacji internetowych i mobilnych. Jednak we współczesnym rozwoju widzieliśmy, jak tryb ciemny, który wyświetla jasny tekst i elementy interfejsu na ciemnym tle, szybko staje się preferencją użytkownika. W tym artykule dowiemy się, jak efektywnie zaimplementować tryb ciemny w aplikacji React na prostej stronie internetowej, korzystając z biblioteki styled-components i wykorzystując niektóre funkcje Reacta, takie jak hooki. Omówimy również plusy i minusy trybu ciemnego oraz dlaczego warto go przyjąć.

Jedną z najczęściej żądanych funkcji oprogramowania jest tryb ciemny (lub tryb nocny, jak to nazywają inni). Tryb ciemny widzimy w aplikacjach, z których korzystamy na co dzień. Od aplikacji mobilnych po aplikacje internetowe, tryb ciemny stał się niezbędny dla firm, które chcą zadbać o oczy swoich użytkowników.

Tryb ciemny to dodatkowa funkcja, która wyświetla w interfejsie głównie ciemne powierzchnie. Większość dużych firm (takich jak YouTube, Twitter i Netflix) przyjęła tryb ciemny w swoich aplikacjach mobilnych i internetowych.

Chociaż nie będziemy zagłębiać się w React i styled-components, podstawowa wiedza na temat Reacta, CSS i styled-components może się przydać. Ten samouczek przyda się tym, którzy chcą ulepszyć swoje aplikacje internetowe, dostosowując się do tych, którzy kochają tryb ciemny.

StackOverflow ogłasza tryb ciemny na Twitterze
StackOverflow ogłasza tryb ciemny na Twitterze (duży podgląd)

Kilka dni przed napisaniem tego artykułu StackOverflow ogłosił wydanie trybu ciemnego, dając użytkownikom możliwość przełączania się między tymi dwoma trybami.

Tryb ciemny zmniejsza zmęczenie oczu i pomaga, gdy przez długi czas pracujesz na komputerze lub telefonie komórkowym.

Co to jest tryb ciemny?

Tryb ciemny to schemat kolorów dowolnego interfejsu, który wyświetla jasny tekst i elementy interfejsu na ciemnym tle, dzięki czemu ekran jest nieco łatwiejszy do oglądania na telefonach komórkowych, tabletach i komputerach. Tryb ciemny zmniejsza ilość światła emitowanego przez ekran, zachowując przy tym minimalne współczynniki kontrastu kolorów wymagane do zachowania czytelności.

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Dlaczego powinieneś przejmować się trybem ciemnym?

Tryb ciemny poprawia ergonomię wizualną, zmniejszając zmęczenie oczu, dostosowując ekran do aktualnych warunków oświetleniowych i zapewniając łatwość użytkowania w nocy lub w ciemnym otoczeniu.

Zanim zaimplementujemy tryb ciemny w naszej aplikacji, przyjrzyjmy się jego zaletom.

Oszczędzanie baterii

Tryb ciemny w aplikacjach internetowych i mobilnych może wydłużyć żywotność baterii urządzenia. Google potwierdziło, że tryb ciemny na ekranach OLED bardzo pomógł w wydłużeniu żywotności baterii.

Na przykład przy jasności 50% tryb ciemny w aplikacji YouTube pozwala zaoszczędzić około 15% więcej energii ekranu niż płaskie białe tło. Przy 100% jasności ekranu ciemny interfejs pozwala zaoszczędzić aż 60% energii ekranu.

Tryb ciemny jest piękny

Tryb ciemny jest piękny i może znacznie poprawić atrakcyjność ekranu.

Podczas gdy większość produktów ma podobny nijaki biały wygląd, tryb ciemny oferuje coś innego, co wydaje się tajemnicze i nowe.

Daje również świetne możliwości prezentowania treści graficznych, takich jak dashboardy, obrazki i zdjęcia w świeży sposób.

Tryb ciemny kontra tryb jasny na Twitterze
Piękno ciemnego trybu Twittera w porównaniu z trybem jasnym (duży podgląd)

Teraz, gdy już wiesz, dlaczego powinieneś zaimplementować tryb ciemny w swojej następnej aplikacji internetowej, przyjrzyjmy się bliżej styled-components, który jest zasobem definiującym ten samouczek.

Tryb ciemny to schemat kolorów dowolnego interfejsu, który wyświetla jasny tekst i elementy interfejsu na ciemnym tle, co ułatwia przeglądanie na telefonach komórkowych, tabletach i komputerach.

Czym są styled-components?

W tym artykule będziemy bardzo często korzystać z biblioteki styled-components. Zawsze istniało wiele sposobów na stylizację nowoczesnej aplikacji internetowej. Istnieje tradycyjna metoda tworzenia stylów na poziomie dokumentu, która obejmuje tworzenie pliku index.css i łączenie go z kodem HTML lub tworzenie stylów w pliku HTML.

Od czasu wprowadzenia CSS-in-JS ostatnio wiele się zmieniło w sposobie stylizacji aplikacji internetowych.

CSS-in-JS odnosi się do wzorca, w którym CSS składa się z JavaScript. Wykorzystuje otagowane literały szablonów do stylizowania komponentów w pliku JavaScript.

Aby dowiedzieć się więcej o CSS-in-JS, zapoznaj się z artykułem Anny Monus na ten temat.

styled-components to biblioteka CSS-in-JS, która umożliwia korzystanie ze wszystkich ulubionych funkcji CSS, w tym zapytań o media, pseudoselektorów i zagnieżdżania.

Dlaczego styled-components?

styled-components powstało z następujących powodów:

  • Bez nazwy klasy piekło
    Zamiast drapać się po głowie, aby znaleźć nazwę klasy dla elementu, styled-components generuje unikalne nazwy klas dla twoich stylów. Nigdy nie będziesz musiał się martwić o błędy ortograficzne lub używanie nazw klas, które nie mają znaczenia.
  • Korzystanie z rekwizytów
    styled-components pozwalają nam rozszerzyć właściwości stylizacji za pomocą parametru props , powszechnie używanego w React — w ten sposób dynamicznie wpływając na odczucie komponentu poprzez stan aplikacji.
  • Obsługuje składnię Sass
    Pisanie składni Sassa po wyjęciu z pudełka bez konieczności konfigurowania jakichkolwiek preprocesorów lub dodatkowych narzędzi do budowania jest możliwe dzięki styled-components. W definicjach stylu możesz użyć znaku & , aby wskazać bieżący komponent, użyć pseudoselektorów i poeksperymentować z zagnieżdżaniem.
  • Tematy
    styled-components mają pełną obsługę motywów dzięki wyeksportowaniu komponentu opakowania ThemeProvider . Ten komponent zapewnia motyw dla wszystkich komponentów React w sobie za pośrednictwem Context API. W drzewie renderowania wszystkie styled-components będą miały dostęp do dostarczonego motywu, nawet jeśli mają wiele poziomów głębokości. Kontynuując ten samouczek, przyjrzymy się bliżej cechom stylizowanych komponentów.

Aby poznać więcej zalet styled-components, zapoznaj się z artykułem Krisa Guzmana.

Wdrażanie trybu ciemnego

W tym artykule zamierzamy zaimplementować tryb ciemny na prostej stronie internetowej podobnej do YouTube.

Aby kontynuować, upewnij się, że sklonowałeś oryginalne repozytorium z gałęzi starter .

Konfiguracja

Zainstalujmy wszystkie zależności w naszym pliku package.json . Z terminala uruchom następujące polecenie:

 npm install

Po pomyślnej instalacji uruchom npm start . Oto jak wygląda strona internetowa bez zaimplementowanego na niej trybu ciemnego.

Strona internetowa, która ma być używana, bez trybu ciemnego
Strona internetowa, która ma być używana, bez trybu ciemnego. (duży podgląd)

Aby zainstalować styled-components , w terminalu uruchom npm install styled-components .

Realizacja

Aby zaimplementować tryb ciemny, musimy stworzyć cztery różne komponenty.

  • Theme
    Zawiera właściwości kolorystyczne naszych jasnych i ciemnych motywów.
  • GlobalStyles
    Zawiera globalne style dla całego dokumentu.
  • Toggler
    To przechowuje element przycisku, który przełącza funkcjonalność.
  • useDarkMode
    Ten niestandardowy hak obsługuje logikę stojącą za zmianą motywu i utrzymywaniem naszego motywu w localStorage.

Komponent motywu

W folderze src zobaczysz komponenty w folderze components . Utwórz plik Themes.js i dodaj do niego następujący kod.

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

Tutaj zdefiniowaliśmy i wyeksportowaliśmy obiekty lightTheme i darkTheme z różnymi zmiennymi kolorów. Zachęcamy do eksperymentowania i dostosowywania zmiennych do własnych potrzeb.

Składnik globalStyles

Pozostając w folderze components , utwórz plik globalStyles.js i dodaj następujący kod:

 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; } `

Zaimportowaliśmy createGlobalStyle z styled-components. Metoda createGlobalStyle zastępuje obecnie przestarzałą metodę injectGlobal z styled-components w wersji 3. Ta metoda generuje komponent React, który po dodaniu do drzewa komponentów wstawi globalne style do dokumentu, w naszym przypadku App.js .

Zdefiniowaliśmy składnik GlobalStyle i przypisaliśmy właściwości background i color do wartości z obiektu motywu. Dlatego za każdym razem, gdy przełączamy przełącznik, wartości będą się zmieniać w zależności od ciemnego motywu lub jasnych obiektów motywu, które przekazujemy do ThemeProvider (który zostanie utworzony później, w miarę postępów).

Właściwość przejścia 0.50s umożliwia nieco bardziej płynną zmianę, dzięki czemu podczas przełączania w tę i z powrotem możemy zobaczyć, jak zachodzą zmiany.

Tworzenie funkcji przełączania motywów

Aby zaimplementować funkcję przełączania motywów, musimy dodać tylko kilka linijek kodu. W pliku App.js dodaj następujący kod (zwróć uwagę, że zaznaczony kod jest tym, co powinieneś dodać):

 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;

Podświetlony kod to ten, który został nowo dodany do App.js . Zaimportowaliśmy ThemeProvider z styled-components . ThemeProvider to składnik pomocniczy w bibliotece styled-components, który zapewnia obsługę motywów. Ten komponent pomocniczy wstrzykuje motyw do wszystkich komponentów React znajdujących się poniżej siebie za pośrednictwem Context API.

W drzewie renderowania wszystkie styled-components będą miały dostęp do dostarczonego motywu, nawet jeśli mają wiele poziomów głębokości. Sprawdź sekcję „Theming”.

Następnie importujemy opakowanie ./components/Globalstyle GlobalStyle Na koniec, od góry, importujemy zarówno obiekty lightTheme , jak i darkTheme z ./components/Themes .

Aby stworzyć metodę przełączania, potrzebujemy stanu, który przechowuje początkową wartość koloru naszego motywu. Dlatego tworzymy stan theme i ustawiamy stan początkowy na light , używając haczyka useState .

Teraz przejdźmy do funkcji przełączania.

Metoda themeToggler używa operatora trójargumentowego do sprawdzenia stanu theme i przełącza pomiędzy ciemnym a jasnym w zależności od wartości warunku.

ThemeProvider , składnik pomocniczy styled-components, otacza wszystko w instrukcji return i wstrzykuje wszystkie składniki poniżej. Pamiętaj, że nasze GlobalStyles wstrzykują style globalne do naszych komponentów; w związku z tym jest wywoływany w składniku opakowania ThemeProvider .

Na koniec stworzyliśmy przycisk ze zdarzeniem onClick , które przypisuje do niego naszą metodę themeToggler .

Zobaczmy jak dotąd wynik.

Tryb ciemny zaimplementowany bez trwałości.
Tryb ciemny zaimplementowany bez trwałości (duży podgląd)

Nasz plik App.js wymaga refaktoryzacji; znaczna część jego kodu nie jest SUCHY. (DRY oznacza „nie powtarzaj się”, podstawową zasadę tworzenia oprogramowania mającą na celu ograniczenie powtórzeń.) Cała logika wydaje się być w App.js ; dobrą praktyką jest oddzielenie naszej logiki dla jasności. Dlatego utworzymy komponent, który obsługuje funkcję przełączania.

Przełącz komponent

Nadal w folderze components utwórz plik Toggler.js i dodaj do niego następujący kod:

 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;

Aby zachować porządek, stylizowaliśmy nasz przycisk przełączania w komponencie Toggle , korzystając z funkcji styled z komponentów styled-components.

To jest wyłącznie do prezentacji; możesz stylizować przycisk według własnego uznania.

Wewnątrz komponentu Toggle przekazujemy dwa rekwizyty:

  • theme zawiera aktualny motyw (jasny lub ciemny);
  • funkcja toggleTheme będzie używana do przełączania się między motywami.

Następnie zwracamy komponent Button i przypisujemy funkcję toggleTheme do zdarzenia onClick .

Na koniec używamy propTypes do zdefiniowania naszych typów, upewniając się, że nasz theme jest string i isRequired , podczas gdy nasz toggleTheme jest func i isRequired .

Używanie niestandardowych hooków ( useDarkMode )

Podczas budowania aplikacji najważniejsza jest skalowalność, co oznacza, że ​​nasza logika biznesowa musi być wielokrotnego użytku, abyśmy mogli z niej korzystać w wielu miejscach, a nawet w różnych projektach.

Dlatego byłoby wspaniale przenieść naszą funkcję przełączania do osobnego komponentu. W tym celu stworzylibyśmy własny niestandardowy hak.

Utwórzmy nowy plik o nazwie useDarkMode.js w folderze components i przenieśmy do niego naszą logikę, wprowadzając kilka poprawek. Dodaj następujący kod do pliku:

 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] };

Dodaliśmy tutaj kilka rzeczy.

  • setMode
    Używamy localStorage do utrzymywania się między sesjami w przeglądarce. Jeśli więc użytkownik wybrał ciemny lub jasny motyw, otrzyma go podczas następnej wizyty w aplikacji lub ponownego załadowania strony. Dlatego ta funkcja ustawia nasz stan i przekazuje theme do localStorage .
  • themeToggler
    Ta funkcja wykorzystuje trójargumentowy operator do sprawdzania stanu motywu i przełącza pomiędzy ciemnym a jasnym w oparciu o prawdziwość warunku.
  • useEffect
    Zaimplementowaliśmy hak useEffect do sprawdzania montażu komponentów. Jeśli użytkownik wcześniej wybrał motyw, przekażemy go naszej funkcji setTheme . Na koniec zwrócimy nasz theme , który zawiera wybrany theme i funkcję themeToggler do przełączania między trybami.

Myślę, że zgodzisz się, że nasz komponent trybu ciemnego wygląda elegancko.

Przejdźmy do App.js na ostatnie poprawki.

 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;

Podświetlony kod został nowo dodany do App.js .

Najpierw importujemy nasz niestandardowy hook, destrukturyzujemy atrybuty theme i themeToggler i ustawiamy je za pomocą funkcji useDarkMode .

Zauważ, że metoda useDarkMode zastępuje nasz stan theme , który początkowo znajdował się w App.js .

Deklarujemy zmienną themeMode , która renderuje jasny lub ciemny motyw w zależności od stanu trybu theme w danym momencie.

Teraz naszemu składnikowi opakowującemu ThemeProvider przypisano ostatnio utworzoną zmienną themeMode do właściwości theme .

I na koniec, w miejsce zwykłego przycisku, przekazujemy komponent Toggle .

Pamiętaj, że w naszym komponencie Toggle zdefiniowaliśmy i stylizowaliśmy przycisk oraz przekazaliśmy im zarówno theme , jak i toggleTheme jako rekwizyty. Tak więc wszystko, co musimy zrobić, to przekazać te rekwizyty odpowiednio do komponentu Toggle , który będzie działał jako nasz przycisk w App.js .

TAk! Nasz tryb ciemny jest ustawiony i utrzymuje się, nie zmieniając koloru, gdy strona jest odświeżana lub odwiedzana w nowej karcie.

Zobaczmy wynik w akcji:

Zaimplementowany tryb ciemny, ale z usterką w kolorze przycisku po ponownym załadowaniu przeglądarki.
Zaimplementowany tryb ciemny, ale z usterką w kolorze przycisku po ponownym załadowaniu przeglądarki. (duży podgląd)

Prawie wszystko działa dobrze, ale jest jedna mała rzecz, którą możemy zrobić, aby nasze doświadczenie było wspaniałe. Przełącz się na ciemny motyw, a następnie ponownie załaduj stronę. Czy widzisz, że niebieski kolor przycisku ładuje się na chwilę przed szarym? Dzieje się tak, ponieważ nasz hook useState początkowo inicjuje light motyw. Następnie useEffect uruchamia się, sprawdza localStorage i dopiero wtedy ustawia theme na dark . Przejdźmy do naszego niestandardowego useDarkMode.js i dodajmy mały kod:

 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 ]
};

Podświetlony kod jest jedynym dodanym do useDarkMode.js . Stworzyliśmy inny stan o nazwie mountedComponent i ustawiliśmy domyślną wartość na false za pomocą haka useState . Następnie w haczyku useEffect ustawiamy stan mountedComponent na true za pomocą setMountedComponent . Na koniec, w tablicy return , uwzględniamy stan mountedComponent .

Na koniec dodajmy trochę kodu w App.js , aby wszystko działało.

 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;

Dodaliśmy nasz stan mountedComponent jako właściwość w haczyku useDarkMode i sprawdziliśmy, czy nasz komponent został zamontowany, ponieważ tak się dzieje w haczyku useEffect . Jeśli jeszcze się to nie wydarzyło, wyrenderujemy pusty div .

Zobaczmy wynik naszej strony internetowej w trybie ciemnym.

Ostateczny wynik trybu ciemnego
Wynik końcowy trybu ciemnego (duży podgląd)

Teraz zauważysz, że w trybie ciemnym, po ponownym załadowaniu strony, kolor przycisku nie zmienia się.

Wniosek

Tryb ciemny staje się coraz bardziej preferowany przez użytkowników, a implementacja go w aplikacji internetowej React jest znacznie łatwiejsza, gdy używasz opakowania motywów ThemeProvider w stylizowanych komponentach. Śmiało i eksperymentuj ze styled-components podczas implementacji trybu ciemnego; możesz dodać ikony zamiast przycisku.

Podziel się swoją opinią i doświadczeniami dotyczącymi funkcji motywów w stylizowanych komponentach w sekcji komentarzy poniżej. Chętnie zobaczę, co wymyślisz!

Obsługiwane repozytorium dla tego artykułu jest dostępne w serwisie GitHub. Sprawdź to również na CodeSandbox.

Bibliografia

  • „Dokumentacja”, styled-components
  • „Stwórz tryb ciemny swojej aplikacji za pomocą Styled Components”, Tom Nolan, Medium