尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深入解析 Scalar useColorMode Hook:Vue 应用中的暗色/亮色模式状态管理

深入解析 Scalar useColorMode Hook:Vue 应用中的暗色/亮色模式状态管理 深入解析 Scalar useColorMode HookVue 应用中的暗色/亮色模式状态管理【免费下载链接】scalarScalar is an open-source API platform: Modern REST API Client Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar导读scalar/use-hooks是 Scalar 开源 API 平台REST API 客户端、API 文档与 OpenAPI 工具链的 Vue 组合式函数composable hook集合useColorMode是其中负责主题色彩模式color mode的核心钩子。它统一处理系统偏好检测、localStorage 持久化、模式切换与 CSS 类应用让 Vue 组件可以零成本接入暗色/亮色主题体系。读完本文你将掌握useColorMode的完整 API、参数优先级规则、SSR/SSG 下的水合hydration安全策略以及它与scalar/helpers/theme底层工具的分工原理。功能概览一个组合式钩子解决暗色模式全链路useColorMode是一个基于 Vue 3 Composition API 的组合式函数其定位在 useColorMode/README.md 中描述得非常清楚A composable hook that provides color mode (dark/light) functionality. Handles system preferences, local storage persistence, and provides methods to toggle and set the color mode. Automatically applies appropriate CSS classes to enable theme switching.翻译过来即它提供色彩模式暗色/亮色功能处理系统偏好、本地存储持久化提供切换toggle与设置set方法并自动应用合适的 CSS 类以启用主题切换。也就是说一个钩子覆盖了暗色模式从「读取偏好 → 解析决策 → 持久化 → 应用到 DOM」的完整链路。其核心源码位于 useColorMode.ts对外导出通过 index.ts 完成同时重新导出了ColorMode与DarkLightMode两个类型。安装与导入useColorMode随scalar/use-hooks包一起发布安装方式见 packages/use-hooks/README.mdnpm add scalar/use-hooks按需导入import { useColorMode } from scalar/use-hooks/useColorMode基本用法一行代码接入主题在原文档给出的最简用法中只需在script setup中调用useColorMode()即可。调用后钩子会自动监听色彩模式变化并把对应的 CSS 类应用到页面上script setup langts import { useColorMode } from scalar/use-hooks/useColorMode // Watches for changes in the color mode and applies the appropriate CSS classes useColorMode() /script template !-- Template goes here -- /template从源码useColorMode.ts可以看到调用时钩子会立即完成一次模式初始化并建立响应式监听colorMode.value overrideColorMode ?? savedColorMode ?? initialColorMode // Watch for color mode or system preference changes and update the body class. watch([colorMode, systemPreference], applyBodyColorMode, { immediate: true })watch的immediate: true意味着挂载即应用一次类名之后任何色彩模式或系统偏好的变化都会同步刷新 DOM。返回值 API 详解useColorMode返回 6 个成员useColorMode.ts其中多个是可写writable的赋值即触发持久化与 DOM 更新返回值类型说明colorModeWritableComputedRefColorMode当前模式可选light \| dark \| system赋值即调用setColorModedarkLightModeWritableComputedRefDarkLightMode已解析的暗/亮模式light \| darksystem会被替换为系统偏好值isDarkModeWritableComputedRefboolean当前是否为暗色模式赋布尔值等价于setColorMode(value ? dark : light)toggleColorMode() void在暗/亮之间切换并写入 localStoragesetColorMode(value: ColorMode) void设置指定模式并写入 localStoragegetSystemModePreference() DarkLightMode读取操作系统偏好light \| dark关键设计system是偏好而非渲染值源码中定义了三种可选项/** Possible color modes */ export type ColorMode light | dark | system而底层scalar/helpers/theme/color-mode中的DarkLightMode只有light | dark两种。system之所以不能直接用于渲染是因为它只是一个「待解析的偏好」——必须先在运行时对操作系统查询一次prefers-color-scheme才能知道最终该渲染成什么颜色。因此darkLightMode这个可写计算属性充当了「解析层」const darkLightMode computedDarkLightMode({ get: () (colorMode.value system ? systemPreference.value : colorMode.value), set: setColorMode, })这意味着当colorMode为system时darkLightMode返回的始终是light或dark中的某一个业务组件只需绑定darkLightMode/isDarkMode就无需关心用户到底选了「跟随系统」还是固定模式。切换与设置一次赋值双写状态toggleColorMode与setColorMode的实现useColorMode.ts遵循「先改内存状态、再写 localStorage」的模式存储键名为colorModefunction toggleColorMode() { // Update state colorMode.value darkLightMode.value dark ? light : dark // Store in local storage if (typeof window undefined) { return } window?.localStorage?.setItem(colorMode, colorMode.value) } function setColorMode(value: ColorMode) { colorMode.value value if (typeof window undefined) { return } window?.localStorage?.setItem(colorMode, colorMode.value) }注意其中的typeof window undefined守卫在服务端渲染SSR/SSG环境下两个方法会安静地只更新内存状态而跳过存储写入从而避免直接抛错。选项参数与优先级规则useColorMode接受一个可选对象包含两个参数useColorMode.tsexport function useColorMode( opts: { /** The initial color mode to use */ initialColorMode?: ColorMode /** Override the color mode */ overrideColorMode?: ColorMode } {}, ) { const { initialColorMode system, overrideColorMode } opts // ... }initialColorMode默认system。当 localStorage 中没有历史记录时的初始模式。overrideColorMode默认undefined。一旦传入将强制锁定模式忽略 localStorage 与initialColorMode。初始化的优先级在源码注释中写得非常明确useColorMode.tsPriority: overrideColorMode - localStorage - initialColorMode即强制覆盖参数 用户上次保存的偏好 默认初始模式。这一规则在测试中得到了完整验证useColorMode.test.tsinitialColorMode会被 localStorage 值覆盖initialColorMode is overridden by localStorage valueoverrideColorMode优先级最高即使调用setColorMode(light)或系统偏好为 lightbody 上依然是dark-mode类respects overrideColorMode option。localStorage 值的安全性从 localStorage 读出的字符串会先经过scalar/validation的 schema 校验union([literal(system), literal(dark), literal(light)])只有合法值才会被采纳非法值如foobar会被忽略并回退到initialColorModeuseColorMode.ts。对应的测试handles unknown localStorage values确认了这一点存入非法值时调用钩子不会抛异常。CSS 类应用机制与底层 helpersuseColorMode本身不直接操作类名而是把脏活交给scalar/helpers/theme/color-mode。这正是 Scalar 主题体系的一个关键设计每个主题的暗色/亮色模式都是成对的一组类选择器而非媒体查询packages/helpers/src/theme/README.md。applyColorMode的实现如下color-mode.tsconst COLOR_MODE_CLASSES { light: light-mode, dark: dark-mode, } as const satisfies RecordDarkLightMode, string export const applyColorMode (mode: DarkLightMode, target: HTMLElement document.body): void { target.classList.toggle(COLOR_MODE_CLASSES.dark, mode dark) target.classList.toggle(COLOR_MODE_CLASSES.light, mode light) }两个类都会被 toggle而不是只添加其中一个这样元素永远不会同时携带light-mode与dark-mode目标元素默认为document.body。useColorMode中对应的applyBodyColorModeuseColorMode.ts会优先使用overrideColorMode其次使用「已解析」后的暗/亮值再调用applyColorMode完成类名交换const classMode overrideColorMode ?? (colorMode.value system ? systemPreference.value : colorMode.value) applyColorMode(classMode dark ? dark : light)系统偏好的读取getSystemColorMode负责读取操作系统偏好color-mode.ts行为分三档无windowSSR返回light与服务端渲染结果一致有window但无matchMedia返回dark该分支在真实浏览器几乎不可达主要是 stub 测试环境正常浏览器通过window.matchMedia((prefers-color-scheme: dark))?.matches判定dark或light。SSR / SSG 下的水合安全策略这是useColorMode最讲究的实现细节之一。模块级共享了systemPreferenceref初始值故意设为lightuseColorMode.tsIt defaults tolightso the first client render matches the server, wherewindow/matchMediado not exist. We resolve the real value inonMountedto avoid a hydration mismatch.即首屏客户端渲染必须与服务端渲染一致服务端没有window/matchMedia只能渲染 light真实系统偏好推迟到onMounted生命周期再解析从而避免水合不一致。同样地无window时 localStorage 读取会直接视为system不会错误地回退到initialColorMode否则服务端与客户端初始值会分叉。对应的测试验证了完整的时序行为useColorMode.test.tsdefers the system preference to onMounted to stay hydration-safe挂载前darkLightMode为light与服务端一致挂载后才升级为真实的暗色偏好keeps the body class aligned with the toggle before mount挂载前 body 类与亮色 toggle 保持一致挂载后 body 类与 toggle 同步升级。在挂载时钩子还会建立系统偏好监听useColorMode.ts并在卸载时清理onMounted(() { systemPreference.value getSystemModePreference() if (typeof window ! undefined typeof window?.matchMedia function) { mediaQuery.value window.matchMedia((prefers-color-scheme: dark)) mediaQuery.value?.addEventListener(change, handleChange) } }) onUnmounted(() { mediaQuery.value?.removeEventListener(change, handleChange) })一旦用户操作系统在运行时切换深浅色handleChange会更新systemPreference进而触发watch重新应用 body 类。测试listens to system preference changes模拟了这一过程系统偏好从 light 变为 dark 后body 类随之从light-mode切换为dark-mode。模块级共享状态多实例一致性colorMode与systemPreference都声明在模块作用域module scope而非函数内部。这意味着所有useColorMode实例共享同一份状态与同一次系统解析结果。测试shares the resolved system preference across instances证明即使第一个实例尚未挂载当第二个实例解析出真实系统偏好后第一个实例的darkLightMode也会立即同步为正确的值——页面中多个组件各自调用该钩子时不会出现状态分叉。实际应用API 参考组件中的主题接入useColorMode已在 Scalar 自身的核心产品中投入使用。例如 API 参考渲染组件 ApiReference.vue 就调用了该钩子来驱动文档界面在暗色/亮色主题间切换。这也印证了该钩子的通用性凡是需要跟随 Scalar 主题体系的 Vue 组件都可以直接复用无需各自重复实现偏好检测与持久化逻辑。测试与可靠性保障该钩子配套了非常完整的 Vitest 测试套件useColorMode.test.ts共 14 组用例覆盖了默认值为system、localStorage 优先级、非法存储值容错toggleColorMode/setColorMode的状态与存储双写colorMode/isDarkMode可写计算属性的赋值行为系统偏好检测与动态变化监听body 类名dark-mode/light-mode的正确应用与互斥initialColorMode与overrideColorMode的优先级无window/document的 SSG 环境不抛错水合安全时序与多实例共享状态matchMedia缺失时的优雅降级。这些用例同时充当了行为契约文档任何对该钩子的重构都必须保持上述语义不变。小结useColorMode用约 140 行代码把暗色模式涉及的「系统偏好解析、localStorage 持久化、优先级决策、CSS 类应用、SSR 水合安全、多实例共享、监听与清理」全部封装完毕。使用时只需遵循两条核心规则默认system跟随系统、优先级为 override localStorage initial。对于需要在 Vue 应用中构建主题切换能力的开发者这个钩子及其测试套件本身就是一份高质量的实现范本。【免费下载链接】scalarScalar is an open-source API platform: Modern REST API Client Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表