2.1 Elements -- useActiveElement

2.1 Elements – useActiveElement

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,
)
  1. source不会发生变化,所以watch不会触发。也就不会因为源变化而触发trigger事件,进一步限制触发的场景。
  2. fn是一个函数,所以自定义的ref没有set方法。
  3. 当触发focus事件的时候,手动调用了trigger方法,这时候依赖activeElement的函数会重新执行,当访问到这个变量时,触发了内部的get函数,也就是() => document?.activeElement,重新获取活跃元素。

因此简单点来说,这个方法,就是document?.activeElement使用一个自定义ref来包裹了一下。

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值