$\underline{主题切换}$ 即通过点击某个组件来切换背景与文字的颜色等 CSS 属性。这要求我们的组件能够控制某个”环境变量”, 且index.css
中的 CSS 应随这个”环境变量”而改变。
要实现这个需求,我们需要解决以下几个问题:
如何定义和管理这个”环境变量”? 组件如何控制这个变量? CSS 如何响应变量的变化? 这些问题的解决方案涉及到几个重要的基础概念,让我们逐一了解。
基础知识 Context 的创建和使用 Context 提供了一种在组件树中共享数据的方式,无需手动在每一层传递 props。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const ThemeContext = createContext<ThemeType | undefined >(undefined );const ThemeProvider = ({ children } ) => { const [theme, setTheme] = useState ('light' ); return ( <ThemeContext.Provider value ={{ theme , setTheme }}> {children} </ThemeContext.Provider > ); }; const ChildComponent = ( ) => { const { theme } = useContext (ThemeContext ); return <div > Current theme: {theme}</div > ; };
因此, 我们可以在src/context/
目录下创建一个ThemeContext
来定义和管理相关上下文.
在其中利用useState
定义主题反转的函数; 在点击按钮组件中使用上述的函数, CSS 变量 CSS 变量(也称为自定义属性)允许我们定义可重用的值:
1 2 3 4 5 6 7 8 9 :root { --primary-color : #007bff ; } .button { background-color : var (--primary-color); }
为了区分CSS的变量与常量, --xx-y
的命名格式是CSS变量的规范; 通过var(<c--xx-y>)
的形式使用CSS变量. HTML data-* 属性 document.documentElement.setAttribute('data-theme', theme)
的作用是在 HTML 根元素上设置一个自定义数据属性:
1 2 3 4 5 6 7 8 9 10 11 12 document .documentElement .setAttribute ('data-theme' , 'dark' );<html data-theme ="dark" > ... </html > [data-theme='dark' ] { --bg-color : #141414 ; }
主题切换实现 主题变量设计 首先设计主题相关的 CSS 变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 :root { --bg-color : #ffffff ; --text-color : #000000 ; --sidebar-bg : #f0f2f5 ; --border-color : #e5e2e2 ; --shadow-color : rgba (0 , 0 , 0 , 0.1 ); } [data-theme='dark' ] { --bg-color : #141414 ; --text-color : #ffffff ; --sidebar-bg : #1f1f1f ; --border-color : #434343 ; --shadow-color : rgba (0 , 0 , 0 , 0.3 ); }
可根据实际需要增减CSS变量.
主题状态管理 创建主题 Context 进行状态管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import React , { createContext, useState, useContext, useEffect } from 'react' ;type Theme = 'light' | 'dark' ;interface ThemeContextType { theme : Theme ; toggleTheme : () => void ; } const ThemeContext = createContext<ThemeContextType | undefined >(undefined );export const ThemeProvider : React .FC <{ children : React .ReactNode }> = ({ children } ) => { const [theme, setTheme] = useState<Theme >(() => { const savedTheme = localStorage .getItem ('theme' ); return (savedTheme as Theme ) || 'light' ; }); useEffect (() => { document .documentElement .setAttribute ('data-theme' , theme); localStorage .setItem ('theme' , theme); }, [theme]); const toggleTheme = ( ) => { setTheme (prev => prev === 'light' ? 'dark' : 'light' ); }; return ( <ThemeContext.Provider value ={{ theme , toggleTheme }}> {children} </ThemeContext.Provider > ); }; export const useTheme = ( ) => { const context = useContext (ThemeContext ); if (context === undefined ) { throw new Error ('useTheme must be used within a ThemeProvider' ); } return context; };
在设置上下文组件之后, 我们需要明确其作用的范围. 我们希望主题变色需要在全局范围内生效, 以react框架为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { ThemeProvider } from './context/ThemeContext'; function App(){ ... return ( <ThemeProvider > <div className = "App" > ... </div > </ThemeProvider > ) }
主题切换组件 实现主题切换按钮:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import React from 'react' ;import { Tooltip } from 'antd' ;import { useTheme } from '../context/ThemeContext' ;import { BsSun , BsMoonStars } from 'react-icons/bs' ;import styles from './ThemeToggle.module.css' ;const ThemeToggle : React .FC = () => { const { theme, toggleTheme } = useTheme (); return ( <Tooltip title ={theme === 'light' ? '切换到暗色模式 ' : '切换到亮色模式 '} placement ="right" > <button className ={styles.themeToggle} onClick ={toggleTheme} aria-label ="Toggle theme" > {theme === 'light' ? <BsMoonStars /> : <BsSun /> } </button > </Tooltip > ); }; export default ThemeToggle ;
此处使用antd
的Tooltip
, 用于在鼠标悬浮按钮组件时显示文字提示; aria-label="Toggle theme"
在<button>
内设置这个属性不是必要的, 但是可以帮助屏幕阅读器读出 “Toggle theme“.组件样式应用 在组件内部使用CSS变量的方式已经在 CSS变量 中介绍, 在此给出示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 .sidebar_container { background-color : var (--sidebar-bg); color : var (--text-color); } .icon_button { color : var (--text-color); background-color : transparent; } .icon_button :hover { background-color : var (--sidebar-hover-color); }
如果某个CSS不需要作为变量进行统一管理, 可以直接使用 属性选择器 进行单独设置:
1 2 3 [data-theme='dark' ] .icon_example { --bg-color : #141414 ; }
总结与参考 通过以上实现,我们构建了一个完整的主题切换系统。关键点包括:
使用 CSS 变量管理主题样式 通过 Context API 实现状态管理 利用 data-theme 属性切换主题 本地存储保持主题持久化 参考 🔗 React Context API CSS Custom Properties HTML data-* Attributes