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

Vue中触发v-if显示列表时输入框失焦的原因探究

问题翻译

我有一个输入框:

<input type="text" maxlength="2" :value="valueHours" @input="calculateTimes($event)" @blur="updateValueHours($event)">

还有一个列表:

<div v-if="open" ... >
  <div v-for="..." >
       list rendered here
  </div>
</div>

在输入框中输入时,我会计算需要在列表中高亮的值:

const calculateTimes = async (evt) => {
    evt.stopImmediatePropagation();
    var highlightIndex = null,
        iterator = 0;
    while (highlightIndex === null && iterator < props.options.length) {
        var time = new Date(props.options[iterator].select_value).getHours()
        if (time.toString().includes(evt.target.value)) {
            highlightIndex = iterator;
            hasFocus.value = iterator;
            open.value = true;
            await checkIfListElementIsRendered(`elem-${iterator}`)
            scrollToFocusedElement();
        }
        iterator++;
    }
}

问题出在设置open.value = true时,输入框似乎失去了焦点,导致:value="valueHours"用当前的空状态覆盖输入内容。预填充valueHours无法解决问题,重新聚焦输入框也会出现异常行为。请问为何通过v-if切换列表显示会导致输入框失焦?

原因分析
  1. v-if的条件渲染特性:v-if是真正的条件渲染——当open从false切换到true时,列表对应的DOM元素会被重新创建并插入到DOM树中。这种DOM结构的突变可能会触发浏览器的焦点重计算机制:比如列表插入的位置与输入框相邻,或者浏览器渲染引擎在处理DOM变更时,意外将焦点从输入框转移走。
  2. 单向绑定的同步问题:你用的是:value单向绑定输入框内容,而不是v-model双向绑定。输入框实时输入的内容并没有同步到valueHours,一旦输入框失焦触发@blur的updateValueHours,如果此时valueHours还是空值,就会把输入框的内容覆盖成valueHours的状态。
解决方案

方案1:同步输入值到绑定变量

在calculateTimes函数开头,先把输入框的实时值同步到valueHours,避免失焦后被空值覆盖:

const calculateTimes = async (evt) => {
  evt.stopImmediatePropagation();
  // 先同步输入值到valueHours
  valueHours.value = evt.target.value;
  
  var highlightIndex = null, iterator = 0;
  while (highlightIndex === null && iterator < props.options.length) {
    var time = new Date(props.options[iterator].select_value).getHours()
    if (time.toString().includes(evt.target.value)) {
      highlightIndex = iterator;
      hasFocus.value = iterator;
      open.value = true;
      await checkIfListElementIsRendered(`elem-${iterator}`)
      scrollToFocusedElement();
    }
    iterator++;
  }
}

如果场景允许,直接改用v-model双向绑定会更省心:

<input type="text" maxlength="2" v-model="valueHours" @input="calculateTimes($event)" @blur="updateValueHours($event)">

方案2:用v-show替代v-if

v-show只是通过CSS的display:none控制元素显示/隐藏,不会销毁或重建DOM,因此不会触发DOM结构变化导致的失焦问题:

<div v-show="open" ... >
  <div v-for="..." >
       list rendered here
  </div>
</div>

注意:如果列表数据量极大,v-show的性能会略低于v-if(因为元素始终存在于DOM中),但小列表场景下完全适用。

方案3:DOM更新后手动聚焦(针对必须用v-if的场景)

如果必须保留v-if,可在设置open.value = true后,通过nextTick等待DOM更新完成,再重新聚焦输入框:

const calculateTimes = async (evt) => {
  evt.stopImmediatePropagation();
  var highlightIndex = null, iterator = 0;
  while (highlightIndex === null && iterator < props.options.length) {
    var time = new Date(props.options[iterator].select_value).getHours()
    if (time.toString().includes(evt.target.value)) {
      highlightIndex = iterator;
      hasFocus.value = iterator;
      open.value = true;
      // 等待DOM更新完成
      await nextTick();
      // 重新聚焦输入框
      evt.target.focus();
      await checkIfListElementIsRendered(`elem-${iterator}`)
      scrollToFocusedElement();
    }
    iterator++;
  }
}

这里用nextTick确保列表DOM已经渲染完成,再执行聚焦操作,能避免之前的异常行为。

内容的提问来源于stack exchange,提问作者temp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 00:07:48