Vue自定义表单组件处理blur事件的关键是明确事件不冒泡特性,需在内部原生元素触发blur时手动$emit('blur')以透传;blur默认不冒泡,focusout才冒泡;拦截场景应通过relatedTarget判断失焦去向并用标志位控制逻辑。

Vue 自定义表单组件中处理焦点失焦事件(blur)的关键,在于明确事件捕获路径、避免意外冒泡干扰父级逻辑,同时确保组件自身能正确响应失焦行为(比如校验、状态更新)。核心不是阻止所有冒泡,而是按需控制——该透传的透传,该拦截的拦截,该主动触发的主动触发。
blur 事件默认不冒泡,但 focusout 会
这是最容易混淆的一点:blur 和 focus 是非冒泡事件(仅在目标元素触发),而 focusout 和 focusin 是冒泡版本。Vue 模板中用 @blur 绑定的正是原生 blur,它不会向上冒泡到父元素。如果你发现 blur “冒泡”了,大概率是:
- 误用了
@focusout(它天然冒泡) - 父组件监听了 document 或 body 的全局 focusout,并做了统一处理
- 自定义组件内部手动调用了
$emit('blur')并未加判断,导致重复触发
在封装输入类组件时主动 emit blur 事件
为保持与原生 input 行为一致,建议在内部原生 input 触发 blur 时,手动 $emit('blur', event)。这样外部使用者可统一用 @blur 监听,无需关心实现细节:
<template>
<input
ref="inputRef"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
@blur="$emit('blur', $event)" <!-- 关键:显式转发 -->
/>
</template>注意:不要直接在根元素上写 @blur 并期望它自动冒泡到组件外——因为组件根元素不是原生 input,blur 不会自动触发;必须由内部真实可聚焦元素(如 input、textarea)触发后手动 emit。
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
立即学习“前端免费学习笔记(深入)”;
需要拦截 blur 场景:防止失焦触发校验或提交
某些交互下你希望暂时禁用 blur 响应,比如点击“取消”按钮时不想触发表单校验。常见做法是结合 relatedTarget 判断失焦去向:
- 在
@blur回调中检查event.relatedTarget是否属于当前操作区域(如弹窗内的按钮) - 若属于预期范围,可调用
event.preventDefault()(对 blur 无效),更可靠的是用标志位跳过后续逻辑 - 推荐方式:加一个
v-click-outside指令或监听 document:click,配合 ref 判断点击是否落在组件外部
组合式 API 中使用 useFocus 管理焦点状态
对于复杂表单组件,可封装一个 useFocus() Hook,统一管理聚焦/失焦状态和事件流:
function useFocus() {
const isFocused = ref(false)
const onFocus = () => { isFocused.value = true }
const onBlur = (e) => {
// 可在此做防抖、延迟判断 relatedTarget 等
setTimeout(() => {
if (!e.currentTarget.contains(e.relatedTarget)) {
isFocused.value = false
// 触发校验等副作用
}
}, 0)
}
return { isFocused, onFocus, onBlur }
}在组件中解构使用,既清晰又便于复用,也避免模板中堆砌逻辑。

















