You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js嵌套事件执行顺序问题:搜索建议框点击失效的排查与解决

Vue.js嵌套事件执行顺序问题:搜索建议框点击失效的排查与解决

我太懂这种糟心的情况了——明明点了建议项,结果搜索框的焦点刚移开,建议框就先消失了,绑定的赋值逻辑根本没机会执行,还不想用setTimeout这种“凑活”的办法对吧?咱们来一步步拆解问题,找到优雅的解决方案。

问题根源:事件执行顺序踩坑

你猜的没错,核心就是事件触发的先后顺序搞的鬼:
当你点击建议项里的<span>时,浏览器的事件流是这样的:

  1. 鼠标从<input>移到<span>,<input>失去焦点,触发容器的focusout事件,把show.value设为false
  2. 这时候Vue会立即更新DOM,把v-if="show"的建议框从页面上移除
  3. 后续的click事件因为目标元素已经不存在,根本不会执行

而且nextTick没用的原因也很简单:它是等待当前事件循环的DOM更新完成后再执行,但focusout是同步触发的,show.value = false已经同步修改了,nextTick回调执行的时候,建议框早就没了。

解决方案1:用mousedown替换click(最简单)

浏览器的事件执行顺序里,mousedown是在focusout之前触发的!我们把建议项的click事件换成mousedown,就能先完成赋值,再处理焦点变化:

<script setup lang="ts">
import { ref } from 'vue'

const show = ref(false)
const model = ref('')

const setFocus = (on: boolean) => {
  show.value = on
}
</script>

<template>
  <div id="container" @focusin="setFocus(true)" @focusout="setFocus(false)">
    <input
      class="input-field"
      type="text"
      placeholder="focus me"
      v-model="model"
    />
    <div id="conditional" v-if="show">
      <!-- 把@click换成@mousedown -->
      <span @mousedown="model = 'it works !'">click me</span>
    </div>
  </div>
</template>

<style scoped>
.input-field {
  border: 1px solid #000;
  padding: 8px;
  border-radius: 8px;
}
#conditional {
  cursor: pointer;
}
</style>

这样一来,mousedown先触发,把model赋值完成,之后哪怕focusout隐藏了建议框,结果已经正确更新了。

解决方案2:判断焦点是否仍在容器内(更严谨)

如果不想改事件类型,我们可以在focusout触发时,检查新获得焦点的元素是否还在容器内部——如果是,说明只是容器内的焦点切换,不需要隐藏建议框:

<script setup lang="ts">
import { ref } from 'vue'

const show = ref(false)
const model = ref('')

const setFocus = (on: boolean, event?: FocusEvent) => {
  if (!on) {
    // 获取容器元素
    const container = document.getElementById('container')
    // 检查失去焦点后,新焦点是否在容器内
    if (container && event?.relatedTarget && container.contains(event.relatedTarget as Node)) {
      return // 焦点还在容器里,不隐藏建议框
    }
  }
  show.value = on
}
</script>

<template>
  <div id="container" @focusin="setFocus(true)" @focusout="setFocus(false, $event)">
    <input
      class="input-field"
      type="text"
      placeholder="focus me"
      v-model="model"
    />
    <div id="conditional" v-if="show">
      <span @click="model = 'it works !'">click me</span>
    </div>
  </div>
</template>

<style scoped>
.input-field {
  border: 1px solid #000;
  padding: 8px;
  border-radius: 8px;
}
#conditional {
  cursor: pointer;
}
</style>

这里用到了FocusEvent的relatedTarget属性,它指向的是触发focusout时新获得焦点的元素。我们通过container.contains()判断这个元素是否在容器内,是的话就不执行隐藏逻辑,这样click事件就能正常触发了。

总结

两种方案都能避开setTimeout的hack:

  • 方案1简单直接,适合大多数场景
  • 方案2更严谨,适合复杂的容器内焦点交互场景

备注:内容来源于stack exchange,提问作者hwk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 18:00:31