☰
VueUse useFocusWithin 完全指南:用 Vue 3 响应式追踪元素焦点范围
2026/10/1 2:03:51 网站建设 项目流程
  • 前端

【免费下载链接】vueuse

Collection of essential Vue Composition Utilities for Vue 3

项目地址:https://gitcode.com/gh_mirrors/vu/vueuse
点击查看免费下载

导读

useFocusWithin是 VueUse 中用于「响应式追踪一个元素或其任意后代元素是否获得焦点」的核心组合式函数,其行为与 CSS 伪类:focus-within完全对齐。它在表单校验、下拉菜单、输入框组、卡片高亮等需要感知「焦点是否落在一个元素子树内部」的场景中非常实用。读完本文,你将掌握useFocusWithin的完整用法、底层事件机制(focusin/focusout+:focus-within匹配)、可配置参数以及如何在真实 Vue 3 项目中落地。

一、它解决什么问题:焦点状态的「范围感知」

原生 DOM 的focus/blur事件只能告诉你「某个具体元素」是否聚焦,却无法回答一个更常见的 UI 问题:

用户当前正在操作我包裹的这个表单 / 下拉面板 / 输入框组吗?

举个例子:一个包含 4 个<input>的表单(姓名、姓氏、邮箱、密码),你希望「只要其中任何一个输入框获得焦点,就高亮整个表单容器、显示帮助提示或激活提交按钮」。如果用原生事件,你需要给 4 个输入框分别绑定监听;而useFocusWithin只需一行代码,就能把「焦点是否落在整个表单子树内」抽象为一个响应式布尔值。

官方文档(packages/core/useFocusWithin/index.md)明确指出,它就是为了匹配:focus-withinCSS 伪类的行为而设计,常见用例正是「在表单元素上查看其任一输入框当前是否获得焦点」。

二、快速上手:追踪一个表单的焦点状态

文档给出的最小可用示例非常直观:用ref声明目标元素,调用useFocusWithin(target)拿到focused响应式引用,再用watch观察它的变化。

<script setup lang="ts"> import { useFocusWithin } from '@vueuse/core' import { ref, watch } from 'vue' const target = ref() const { focused } = useFocusWithin(target) watch(focused, (focused) => { if (focused) console.log('Target contains the focused element') else console.log('Target does NOT contain the focused element') }) </script> <template> <form ref="target"> <input type="text" placeholder="First Name"> <input type="text" placeholder="Last Name"> <input type="text" placeholder="Email"> <input type="text" placeholder="Password"> </form> </template>

这段代码说明三个要点:

  • target可以是模板 ref、响应式 ref 或组件实例,源码通过 unrefElement 统一解析为真实 DOM 元素($el会被自动解包);
  • 返回值解构出的focused是一个ComputedRef<boolean>,由源码中的UseFocusWithinReturn接口定义(见 index.ts);
  • 只要表单内任意输入框获得焦点,focused即为true;当焦点离开整个表单子树时才变为false。

仓库自带的真实演示(demo.vue)展示了更完整的模板写法:使用 Vue 3.5+ 的useTemplateRef('target')替代普通ref(),并在界面上实时显示Focus in form的布尔状态。

三、进阶场景:与模板 ref 和条件渲染配合

对于用v-if/v-for动态渲染的目标元素,推荐使用useTemplateRef结合模板 ref 的方式(仓库演示 demo.vue 正是这样写的):

<script setup lang="ts"> import { useFocusWithin } from '@vueuse/core' import { useTemplateRef } from 'vue' const target = useTemplateRef('target') const { focused } = useFocusWithin(target) </script> <template> <form ref="target" class="form-card"> <input type="text" placeholder="First Name"> <input type="text" placeholder="Last Name"> <input type="text" placeholder="Email"> <input type="text" placeholder="Password"> </form> <div class="status"> Focus in form: <strong>{{ focused }}</strong> </div> </template>

由于目标元素是响应式解析的(内部用computed(() => unrefElement(target))),即使目标在渲染后才挂载、或中途被替换,监听依然会自动跟随最新元素,无需手动重建组合式函数。

四、参数详解:window 可配置项与边界行为

useFocusWithin(target, options)的第二个参数options实现了 VueUse 标准的ConfigurableWindow接口(见 packages/core/_configurable.ts):

参数类型默认值说明
windowWindowdefaultWindow(客户端为window,SSR 下为undefined)指定自定义window实例,例如在 iframe、测试环境或 mock 场景中注入

两个关键边界行为,均有源码(index.ts)与测试(index.test.ts)双重佐证:

  1. SSR 安全:当window不存在(如服务端渲染)或document.activeElement无效时,函数直接返回初始状态{ focused }(恒为false),不会注册任何事件监听,也不会抛错。
  2. activeElement 无效时恒为 false:测试用例通过new Proxy(window, ...)模拟document.activeElement === null的场景,验证了即便parent.focus()、child.focus()被调用,focused依然保持false——因为状态追踪依赖useActiveElement提供当前活动元素的有效性前提。

五、源码深度解析:focusin/focusout + :focus-within 的双保险

useFocusWithin的实现非常精巧,值得逐行拆解(完整源码见 packages/core/useFocusWithin/index.ts):

const EVENT_FOCUS_IN = 'focusin' const EVENT_FOCUS_OUT = 'focusout' const PSEUDO_CLASS_FOCUS_WITHIN = ':focus-within' const targetElement = computed(() => unrefElement(target)) const _focused = shallowRef(false) const focused = computed(() => _focused.value) const activeElement = useActiveElement(options) const listenerOptions = { passive: true } useEventListener(targetElement, EVENT_FOCUS_IN, () => _focused.value = true, listenerOptions) useEventListener(targetElement, EVENT_FOCUS_OUT, () => _focused.value = targetElement.value?.matches?.(PSEUDO_CLASS_FOCUS_WITHIN) ?? false, listenerOptions)

其核心机制可以拆解为四层:

  1. 选用focusin/focusout而非focus/blur:这两个事件支持冒泡,因此只需在目标元素(如<form>)上监听一次,就能捕获子树内任意后代元素(如<input>)的焦点进出,无需逐个后代绑定。这是整个函数「范围感知」能力的基础。

  2. useActiveElement提供活动元素前提:函数调用 useActiveElement 响应式追踪document.activeElement(支持deep: true穿透 shadow DOM 查找深层活动元素),并以此判断当前环境是否具备有效的焦点状态,是前面提到的「activeElement 无效时恒为 false」行为的来源。

  3. focusout时的:focus-within双保险:focusout触发时,事件的目标可能是离开焦点的子元素,此时不能简单置为false——因为焦点可能只是从input A移到了同表单的input B。因此源码调用targetElement.matches(':focus-within')做最终判定:只要目标元素此刻仍匹配:focus-within伪类,就说明焦点仍在子树内部,focused保持true。这正是测试用例「后代之间切换焦点时状态持续为 true」(index.test.ts)所验证的行为。

  4. useEventListener自动管理生命周期:监听通过 useEventListener 注册,在组件卸载时自动移除监听器,不会产生内存泄漏;passive: true保证监听不阻塞滚动等默认行为。

六、测试用例验证:行为即规范

仓库的 index.test.ts 用五个用例完整定义了该函数的契约,可作为理解行为边界的权威参考:

测试用例验证行为
should be defined函数导出存在性
should initialize properly初始状态focused为false
should track the state of the target itself目标元素自身focus()后为true,blur()后为false
should track the state of the targets descendants子元素、孙元素(child、grandchild)聚焦同样触发true
should track the state while the descendants switch focus state后代之间连续切换焦点(child→child2→child)时focused持续为true,离开后归false
should the state of target always be falsy when document.activeElement invalidmockactiveElement为null时,无论谁聚焦都恒为false

测试还揭示了两个实现细节:测试 DOM 中所有节点都设置了tabIndex = 0以确保可聚焦;结构上构造了form → div → input的祖孙三层嵌套,专门用于验证「后代任意层级聚焦都能被捕获」。

七、实战组合建议与相关函数

useFocusWithin属于 VueUse core 的 Sensors(传感器)类别,从 packages/core/index.ts 的导出可以看出它与useFocus、useActiveElement、useFocusVisible等函数同族。实际项目中可这样组合使用:

  • 焦点高亮表单:focused为true时给表单容器追加高亮 class,用 CSS 过渡实现视觉反馈;
  • 下拉/弹层自动关闭:结合useClickOutside思路,在focused变为false时收起面板;
  • 键盘导航状态:在watch(focused, ...)中联动快捷键提示或无障碍 aria 状态;
  • 与useFocus对比:useFocus(见 useFocus/index.ts)追踪的是「单个元素自身的 focus/blur」且返回可写的focused(可编程聚焦/失焦),而useFocusWithin追踪的是「元素子树整体」且返回只读的focused——两者恰好互补。

八、注意事项与适用前提

  • 仅浏览器环境生效:依赖focusin/focusout事件与Element.matches(),SSR 下直接返回恒false的初始状态(源码 index.ts 已保证无副作用、不报错);
  • 返回值为只读:与useFocus不同,useFocusWithin的focused是ComputedRef<boolean>,只能读取,不能通过赋值来聚焦元素;
  • iframe 场景:如需在 iframe 内追踪焦点,可传入自定义window选项;
  • 无需手动清理:监听器生命周期由useEventListener自动托管,组件卸载即解除。

结合官方文档、源码实现、测试用例与演示页,useFocusWithin是一个「接口极简、语义清晰、边界严谨」的实用工具:一行调用即可获得整个元素子树的焦点状态,值得在表单、面板与交互组件的状态管理中优先采用。

  • 前端

【免费下载链接】vueuse

Collection of essential Vue Composition Utilities for Vue 3

项目地址:https://gitcode.com/gh_mirrors/vu/vueuse
点击查看免费下载
上一篇:使用 aws_cognito_identity_pool 数据源查询 Amazon Cognito 身份池:terraform-provider-aws 实践指南
下一篇:蚂蚁森林自动化脚本终极指南:5步轻松实现全自动能量收取

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询