为了对比 React 和 Vue 在相同项目上的开发体验,我决定从零开始同时手撸 React 和 Vue 后台
代码仓库会和本系列文章同时更新,欢迎 fork,感谢 star
往期:
从零开始同时手撸 React 和 Vue 后台 01 —— 创建项目
主题切换需要考虑:
antd 的主题切换可以通过 antd-style 轻松实现
文档:https://ant-design.github.io/antd-style/guide/switch-theme
import { ThemeProvider } from 'antd-style';export default () => { return ( <ThemeProvider themeMode={'light'}> <App /> </ThemeProvider> );};
Tailwind 的主题切换可以通过在 html 根元素的 class 上增删 dark 实现
文档:https://tailwindcss.com/docs/dark-mode
<!-- Dark mode not enabled --><html><body> <!-- Will be white --> <div class="bg-white dark:bg-black"> <!-- ... --> </div></body></html><!-- Dark mode enabled --><html class="dark"><body> <!-- Will be black --> <div class="bg-white dark:bg-black"> <!-- ... --> </div></body></html>
至于配置的存储,我们选择将其保存在 cookie 中,这样可以在服务端渲染时就能知道当前的主题,从而避免客户端生成的 HTML 和服务端生成的 HTML 不一致。
完整代码请查看代码仓库
'use client';import { ReactNode, createContext, useEffect, useState } from 'react';import { appConfig } from '@/configs/appConfig';import { useCookieValue } from '../hooks/useCookieValue';import { useSystemTheme } from '../hooks/useSystemTheme';import { Settings } from '../types/settings';import { isDarkTheme } from '../utils/theme';export type SettingsContextProps = { settings: Settings; updateSettings: (settings: Partial<Settings>) => void;};type SettingsProviderProps = { children: ReactNode; serverSettingsCookie: Settings;};export const SettingsContext = createContext<SettingsContextProps | null>(null);export const SettingsProvider = ({ children, serverSettingsCookie,}: SettingsProviderProps) => { const systemTheme = useSystemTheme(); const [settingsCookie, updateSettingsCookie] = useCookieValue( appConfig.settingsCookieName, serverSettingsCookie ); const [settings, setSettings] = useState<Settings>(settingsCookie); const updateSettings = (_settings: Partial<Settings>) => { const newVal = { ...settings, ..._settings }; setSettings(newVal); updateSettingsCookie(newVal); }; useEffect(() => { updateSettings({ systemTheme, }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [systemTheme]); useEffect(() => { if ( isDarkTheme(settings.themeMode ?? appConfig.defaultThemeMode, systemTheme) ) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }, [settings.themeMode, systemTheme]); return ( <SettingsContext.Provider value={{ settings: settings, updateSettings }}> {children} </SettingsContext.Provider> );};
element-plus 的暗色模式只需在 html 上添加一个名为 dark 的类,同时引入 css 文件
文档:https://element-plus.org/zh-CN/guide/dark-mode.html
<html class="dark"> <head></head> <body></body></html>
// main.tsimport 'element-plus/theme-chalk/dark/css-vars.css'
unocss 的暗色模式也可以通过在 html 上添加一个名为 dark 的类来实现
文档:https://unocss.dev/presets/mini
于是,我们通过在 html 上增删 dark 类,可以同时切换element-plus 和 unocss 的主题
而 vueuse 提供的 useDark 可以帮我们轻松实现在 html 上 增删 dark 类
文档:https://vueuse.org/core/useDark/
对于 CSR 来说,没有特别的需求,所以我们将配置存储在 LocalStorage 中
完整代码请查看代码仓库
import { appConfig } from '@/configs/appConfig'import type { Settings } from '../types/settings'import { isDarkTheme } from '../utils/theme'export const useSettings = defineStore('settings', () => { const settings = useLocalStorage<Settings>(appConfig.settingsStorageName, { themeMode: appConfig.defaultThemeMode, systemTheme: appConfig.defaultSystemTheme }) const isSystemThemeDark = usePreferredDark() const isDark = useDark() watch( settings, ({ themeMode, systemTheme }) => { isDark.value = isDarkTheme(themeMode, systemTheme) }, { immediate: true, deep: true } ) watch( isSystemThemeDark, (val) => { settings.value.systemTheme = val ? 'dark' : 'light' }, { immediate: true } ) const updateSettings = (_settings: Partial<Settings>) => { Object.assign(settings.value, _settings) } return { settings, updateSettings }})
至此,我们实现了明暗主题切换
接下去,我们会实现基本色的动态切换