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切换列表显示会导致输入框失焦?
原因分析
- v-if的条件渲染特性:v-if是真正的条件渲染——当
open从false切换到true时,列表对应的DOM元素会被重新创建并插入到DOM树中。这种DOM结构的突变可能会触发浏览器的焦点重计算机制:比如列表插入的位置与输入框相邻,或者浏览器渲染引擎在处理DOM变更时,意外将焦点从输入框转移走。 - 单向绑定的同步问题:你用的是
: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
相关产品推荐
相关产品推荐

