3.2 Browser – useColorMode
https://vueuse.org/core/useColorMode/
作用
可以自动持久化的响应式颜色主题模式。
官方示例
import { useColorMode } from '@vueuse/core'const mode = useColorMode() // Ref<'dark' | 'light'>
默认情况下,它将使用usePreferredDark
(又名自动模式)与用户的浏览器首选项匹配。
当读取ref
时,它将默认返回当前的颜色模式(暗,亮或您的自定义模式)。
通过启用emitAuto
选项,可以将自动模式包含在返回的模式中。
当写入ref
时,它将触发DOM
更新并将颜色模式持久化到本地存储(或您的自定义存储)。你可以传递auto
来设置回自动模式。
mode.value // 'dark' | 'light'mode.value = 'dark' // change to dark mode and persistmode.value = 'auto' // change to auto mode
- 配置
import { useColorMode } from '@vueuse/core'const mode = useColorMode({attribute: 'theme',modes: {// 自定义模式,默认为'dark' | 'light'dim: 'dim',cafe: 'cafe',},
}) // Ref<'dark' | 'light' | 'dim' | 'cafe'>
- 组件用法。
提供了非渲染组件,代码在@vueuse/components
包中。
<UseColorMode v-slot="{ mode }"><button @click="mode = mode === 'dark' ? 'light' : 'dark'">Mode {{ mode }}</button>
</UseColorMode>
源码分析
代码地址:https://github.com/vueuse/vueuse/blob/main/packages/core/useColorMode/index.ts。
这个函数主要有两个用处:
- 获取当前的颜色模式
- 修改当前的颜色模式
- 先看第一步:初始化颜色模式,优先级是 存储 > 用户传递,同时监听模式变化。
export function useColorMode<T extends string = BasicColorSchema>(options: UseColorModeOptions<T> = {}) {const {selector = 'html',attribute = 'class',initialValue = 'auto',window = defaultWindow,storage,storageKey = 'vueuse-color-scheme',listenToStorageChanges = true,storageRef,emitAuto,} = optionsconst modes = {auto: '',light: 'light',dark: 'dark',...options.modes || {},} as Record<BasicColorSchema | T, string>// 1 判断浏览器的默认模式是不是dark模式const preferredDark = usePreferredDark({ window })const preferredMode = computed(() => preferredDark.value ? 'dark' : 'light')/*** 2 如果用户传递了storageRef,直接使用* 如果没传,看storageKey == null是否成立,因为有默认值,所以一般不成立* 那么就会从storage中取值,根据useStorage的用法,如果localStorage中有值,直接返回localStorage中存储的值;* 如果没有,使用默认值'auto',并且写入localStorage* 在这里store = 'auto'*/const store = storageRef || (storageKey == null? ref(initialValue) as Ref<T | BasicColorSchema>: useStorage<T | BasicColorSchema>(storageKey, initialValue as BasicColorSchema, storage, { window, listenToStorageChanges }))/*** 3 state的结果和preferredMode以及store都有关系。* 也就是浏览器默认模式变化(比如19点钟由light变成了dark),以及用户在其他地方修改了localStorage中值,都会影响state。* preferredMode 和 store的响应式,由 usePreferredDark和 useStorage这两个 hook来维护*/const state = computed<T | BasicColorSchema>({get() {// 如果!emitAuto,那么默认就是浏览器的首选项,也就是说不存在'auto'这个选项return store.value === 'auto' && !emitAuto? preferredMode.value: store.value},set(v) {store.value = v},})// ....../*** 4 监听state的变化,回调onChanged方法。*/watch(state, onChanged, { flush: 'post', immediate: true })if (emitAuto)watch(preferredMode, () => onChanged(state.value), { flush: 'post' })// 5 如果是在组件中调用,那么在 onMounted钩子中执行 onChangedtryOnMounted(() => onChanged(state.value))return state
}
- 再看第二步,当
state
变化了,触发onChanged
回调。
这里可以引起变化的操作包括:
- 用户主动修改
- 颜色模式跟随系统变化
localStorage
中存储的模式发生了变化
/**
* 1 watch 触发了 onChanged方法,onChanged调用了 defaultOnChanged
*/
function onChanged(mode: T | BasicColorSchema) {if (options.onChanged)options.onChanged(mode, defaultOnChanged)elsedefaultOnChanged(mode)}/**
* 2 看一下上面的默认值,假设 resolvedMode是 dark
* 这个函数的意思是:把 html的 class改成 'dark'
*/
function defaultOnChanged(mode: T | BasicColorSchema) {const resolvedMode = mode === 'auto' ? preferredMode.value : modeupdateHTMLAttrs(selector, attribute, modes[resolvedMode] ?? resolvedMode)
}// 不用管这个函数,简单地认为是执行回调函数即可
const updateHTMLAttrs = getSSRHandler('updateHTMLAttrs',(selector, attribute, value) => {const el = window?.document.querySelector(selector)if (!el)return/*** 3 接下来逐行解释一下*/ if (attribute === 'class') {// 以空格分割value,因为value是类名,所以是空格分割。在这里 current = ['dark']const current = value.split(/\s/g)Object.values(modes) // ['', 'light', 'dark', 'dark contrast', 'cafe'].flatMap(i => (i || '').split(/\s/g)) // ['', 'light', 'dark', 'dark', 'contrast', 'cafe'].filter(Boolean) // ['light', 'dark', 'dark', 'contrast', 'cafe'].forEach((v) => {if (current.includes(v)) // eg: if(['dark'].includes(light))el.classList.add(v) // classList 增加最新的 classelseel.classList.remove(v) // classList 删除以前设置过的 class})}else {el.setAttribute(attribute, value)}})