使用 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