useColorMode/useDark 不同主题模式的切换 #
现在很多网站暗色/亮色模式,都是标配,也就衍生出了这个模式切换的 hook。
useDark
是基于 useColorMode
的二次封装 所以只要阅读 useColorMode
即可
useColorMode #
传入参数方式 #
主要传入一个配置对象,所有的配置都是可选的,如果不传入,默认使用默认配置
export interface UseColorModeOptions<T extends string = BasicColorSchema> extends StorageOptions<T | BasicColorSchema> {
selector?: string // 应用于的目标元素的CSS选择器 默认 -> html
attribute?: string // 应用目标元素的HTML属性 默认 -> class
modes?: Partial<Record<T | BasicColorSchema, string>> // 向属性添加值时的前缀
// 用于处理更新的自定义处理程序。指定后,将覆盖默认行为 默认 -> undefined
onChanged?: (mode: T | BasicColorSchema, defaultHandler:((mode: T | BasicColorSchema) => void)) => void
storageRef?: Ref<T | BasicColorSchema> // 自定义存储 ref
storageKey?: string | null // 自定义存储键 默认 -> 'vueuse-color-scheme'
storage?: StorageLike// 自定义存储
emitAuto?: boolean // 当设置为'true'时,首选模式不会转换为'light'或'dark' 默认 -> false
}
主体逻辑 #
- 首先从传入的
options
解构并初始化默认值
const {
selector = 'html',
attribute = 'class',
window = defaultWindow,
storage,
storageKey = 'vueuse-color-scheme',
listenToStorageChanges = true,
storageRef,
emitAuto,
} = options
- 根据传入的
options.modes
和默认的 modes 合并
const modes = {
auto: '',
light: 'light',
dark: 'dark',
...options.modes || {},
} as Record<BasicColorSchema | T, string>
- 根据浏览器或者个人配置是否首选黑暗模式
const preferredDark = usePreferredDark({ window })
const preferredMode = computed(() => preferredDark.value ? 'dark' : 'light')
- 判断是否使用持久化缓存模式配置,首选是使用用户传入的类缓存仓库,如果没传则看是否清空了缓存 key,如果清空了则使用临时 ref 不进行缓存,如果没有更改则使用默认的配置,
listenToStorageChanges
监听缓存修改同步更改状态
const store = storageRef || (storageKey == null
? ref('auto') as Ref<T | BasicColorSchema>
: useStorage<T | BasicColorSchema>(storageKey, 'auto', storage, { window, listenToStorageChanges }))
- 保存当前模式并返回,在默认情况下如果当前模式是
auto
则是读取用户的首选浏览器首选配置,如果启用了emitAuto
则 auto 模式失效
const state = computed<T | BasicColorSchema>({
get() {
return store.value === 'auto' && !emitAuto
? preferredMode.value
: store.value
},
set(v) {
store.value = v
},
})
- 为了兼容服务端渲染所以需要确认是否在
__vueuse_ssr_handlers__
中已经存在过,如果存在则直接调用已经存储的方法
const updateHTMLAttrs = getSSRHandler(
'updateHTMLAttrs',
(selector, attribute, value) => {
...
})
- 启动一个监听器,根据是否传入了
options.onChanged
来确认是否覆盖默认触发行为,如果传入了,则提供当前修改后的模式以及默认触发行为
function defaultOnChanged(mode: T | BasicColorSchema) {
const resolvedMode = mode === 'auto' ? preferredMode.value : mode
updateHTMLAttrs(selector, attribute, modes[resolvedMode] ?? resolvedMode)
}
function onChanged(mode: T | BasicColorSchema) {
if (options.onChanged)
options.onChanged(mode, defaultOnChanged)
else
defaultOnChanged(mode)
}
watch(state, onChanged, { flush: 'post', immediate: true })
- 默认触发行为,则是根据是否修改了设置属性来触发对应的行为,如果值是默认的
class
则会相对应的添加以及删除类名,如果设置了别的属性则直接设置相对应的属性
const el = window?.document.querySelector(selector)
if (!el)
return
if (attribute === 'class') {
const current = value.split(/\s/g)
Object.values(modes)
.flatMap(i => (i || '').split(/\s/g))
.filter(Boolean)
.forEach((v) => {
if (current.includes(v))
el.classList.add(v)
else
el.classList.remove(v)
})
}
else {
el.setAttribute(attribute, value)
}
- 尝试在组件
onMounted
的时候初始化 hook 功能
- Q: 为什么是尝试呢?
- A: 因为在有些情况下不存在组件生命周期,则需要在 DOM 成功挂载之后才能进行处理