手写VueUse的useFocus函数实现

122 阅读1分钟

以下是手写的 useFocus 函数的实现,该函数用于跟踪元素的焦点状态并提供主动聚焦/失焦的方法:

import { ref, watch, onUnmounted, unref, type Ref, type ComponentPublicInstance } from 'vue';

export function useFocus(target?: Ref<HTMLElement | ComponentPublicInstance | null>) {
  const focused = ref(false);
  const element = target ?? ref<HTMLElement | ComponentPublicInstance | null>(null);

  const getElement = (): HTMLElement | null => {
    const el = unref(element);
    return (el as ComponentPublicInstance)?.$el ?? el;
  };

  const onFocus = () => {
    focused.value = true;
  };
  const onBlur = () => {
    focused.value = false;
  };

  const addListeners = (el: HTMLElement | null) => {
    if (el) {
      el.addEventListener('focus', onFocus);
      el.addEventListener('blur', onBlur);
    }
  };

  const removeListeners = (el: HTMLElement | null) => {
    if (el) {
      el.removeEventListener('focus', onFocus);
      el.removeEventListener('blur', onBlur);
    }
  };

  watch(
    () => getElement(),
    (newEl, oldEl) => {
      removeListeners(oldEl);
      addListeners(newEl);
    },
    { immediate: true }
  );

  const focus = () => {
    const el = getElement();
    el?.focus();
  };

  const blur = () => {
    const el = getElement();
    el?.blur();
  };

  onUnmounted(() => {
    removeListeners(getElement());
  });

  return {
    focused,
    focus,
    blur,
    element: element as Ref<HTMLElement | ComponentPublicInstance | null>,
  };
}

使用示例:

<template>
  <!-- 使用返回的 element 绑定模板 -->
  <input v-if="!customEl" :ref="element" />
  
  <!-- 使用自定义 ref 绑定 -->
  <textarea v-if="customEl" :ref="el"></textarea>
  
  <p>Focused: {{ focused }}</p>
  <button @click="focus">手动聚焦</button>
  <button @click="blur">手动失焦</button>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useFocus } from './useFocus';

// 使用方式 1:自动绑定
const { focused, element, focus, blur } = useFocus();

// 使用方式 2:自定义元素引用
const el = ref<HTMLElement | null>(null);
const { focused, focus, blur } = useFocus(el);
const customEl = ref(false);
</script>

实现细节说明:

  1. 响应式状态

    • focused: 使用 ref 存储当前焦点状态
    • element: 处理传入的 target 或创建默认 ref
  2. 元素处理

    • getElement() 方法处理 Vue 组件实例,自动获取其根 DOM 元素
    • 使用 unref() 处理可能被嵌套的 ref
  3. 事件监听

    • 通过 watch 监听元素变化,自动更新事件监听器
    • 在卸载时使用 onUnmounted 清理事件监听
  4. 主动控制

    • 提供 focus()blur() 方法手动控制焦点
    • 兼容常规 HTML 元素和 Vue 组件
  5. 类型安全

    • 完整 TypeScript 类型标注
    • 支持 HTMLElement 和 Vue 组件实例