https://vueuse.org/useActiveElement
响应式的document.activeElement
,配合一些特殊的标识,可以知道当前活跃的是哪个元素。
import { useActiveElement } from '@vueuse/core'
const activeElement = useActiveElement()
watch(activeElement, (el) => {
console.log('focus changed to', el)
})
这里再增加一下demo
的示例,来更清晰的展示它的用法。通过dataset?.id
识别活跃的元素。
const activeElement = useActiveElement()
const key = computed(() => activeElement.value?.dataset?.id || 'null')
也提供了一个无渲染组件。在@vueuse/components
包中。
<UseActiveElement v-slot="{ element }">
Active element is {{ element.dataset.id }}
UseActiveElement>
export function useActiveElement<T extends HTMLElement>(options: UseActiveElementOptions = {}) {
const { window = defaultWindow } = options
const document = options.document ?? window?.document
/**
* computedWithControl这个函数在下面进行分析
* 作用:允许你获取计算属性的值,还能在需要时手动触发其重新计算(使用trigger方法)
* 这在变量不通过vue响应式系统变更时非常有用
*/
const activeElement = computedWithControl(
() => null,
() => document?.activeElement as T | null | undefined,
)
if (window) {
useEventListener(window, 'blur', (event) => {
// 如果焦点从一个元素移动到了另一个元素,relatedTarget会指向那个获得焦点的元素。
// 如果有新的聚焦的元素,这个函数就什么都不做。这是因为focus的时候,同样触发了trigger,这里就不用重复触发了
if (event.relatedTarget !== null)
return
activeElement.trigger()
}, true)
// 如果触发了focus事件,执行 activeElement.trigger方法。这个和computedWithControl有关。往下看。
useEventListener(window, 'focus', activeElement.trigger, true)
}
return activeElement
}
下面看一下computedWithControl
方法。
/**
* 能够精确控制的计算属性
*/
export function computedWithControl<T, S>(
source: WatchSource<S> | WatchSource<S>[],
fn: ComputedGetter<T> | WritableComputedOptions<T>,
) {
let v: T = undefined!
let track: Fn
let trigger: Fn
/**
* dirty是一个标识,来表示值是否修改过。
*/
const dirty = ref(true)
/**
* 可以看到,当触发更新函数的时候,dirty.value = true表示值已经修改过了。
* 所以之后取值的时候就需要重新计算一下。这相当于一个缓存效果。
*/
const update = () => {
dirty.value = true
trigger()
}
/**
* 1 通过watch监听source的变化,并在变化时调用update函数。
* 这里使用{ flush: 'sync' }选项,意味着在观察者触发时立即执行更新,而不是等待DOM更新后。
*/
watch(source, update, { flush: 'sync' })
const get = isFunction(fn) ? fn : fn.get
const set = isFunction(fn) ? undefined : fn.set
/**
* @see https://cn.vuejs.org/api/reactivity-advanced.html#customref
* 2 创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
*/
const result = customRef<T>((_track, _trigger) => {
track = _track
trigger = _trigger
return {
/**
* 2.1 如果值发生了更改,重新计算,并标记dirty.value = false,意味着值是最新的
* 触发依赖收集track(),收集哪些函数使用了这个变量。在trigger的时候,重新执行这些函数。
*/
get() {
if (dirty.value) {
v = get()
dirty.value = false
}
track()
return v
},
/**
* 2.2 set的时候,只是重新赋值,而没有触发trigger。这也就是说触发的时机完全给到调用者。
*/
set(v) {
set?.(v)
},
}
}) as ComputedRefWithControl<T>
/**
* 3 把update方法挂到对象上,方便调用者使用。
*/
if (Object.isExtensible(result))
result.trigger = update
return result
}
⚠️:在useActiveElement
这个hook
中,调用方式如下
const activeElement = computedWithControl(
() => null,
() => document?.activeElement as T | null | undefined,
)
source
不会发生变化,所以watch
不会触发。也就不会因为源变化而触发trigger
事件,进一步限制触发的场景。fn
是一个函数,所以自定义的ref
没有set
方法。focus
事件的时候,手动调用了trigger
方法,这时候依赖activeElement
的函数会重新执行,当访问到这个变量时,触发了内部的get
函数,也就是() => document?.activeElement
,重新获取活跃元素。因此简单点来说,这个方法,就是document?.activeElement
使用一个自定义ref
来包裹了一下。