问题概述:
最近在做Vue2升级Vue3的需求,发现一个el-date-picker日期选择组件失效了。选择日期后页面有回显,但不会触发change事件的回调函数,然而清除日期后又会触发change。神奇的是页面上有两个el-date-picker组件,另一个却正常。下面是代码:
<el-date-picker
v-model="endTime"
type="datetimerange"
value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']"
@change="search"
>
网上搜索后得到如下的解决方法:将change事件改为input,获取input的value值来修改v-model的值。
<el-date-picker
v-model="endTime"
type="datetimerange"
value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']"
size="mini"
@input="searchWithEndTime"
>
searchWithEndTime(value) {
console.log(this.endTime, value)
this.endTime = value;
this.search();
},
虽然input事件会触发,但打印v-model的值会得到null。
源码阅读
为了搞明白这个问题,找到了ElementUI中这个组件的源码。
<el-input
v-bind="firstInputId"
class="el-date-editor"
:class="'el-date-editor--' + type"
:readonly="!editable || readonly || type === 'dates' || type === 'week'"
:disabled="pickerDisabled"
:size="pickerSize"
:name="name"
v-if="!ranged"
v-clickoutside="handleClose"
:placeholder="placeholder"
@focus="handleFocus"
@keydown="handleKeydown"
:value="displayValue"
@input="(value) => (userInput = value)"
@change="handleChange"
@mouseenter="handleMouseEnter"
@mouseleave="showClose = false"
:validateEvent="false"
ref="reference"
>
其中值得注意的是displayValue这个值,这个值会展示在页面上,是一个computed
displayValue() {
// ...
// 此处省略
if (Array.isArray(this.userInput)) {
return [
this.userInput[0] || (formattedValue && formattedValue[0]) || '',
this.userInput[1] || (formattedValue && formattedValue[1]) || '',
]
} else if (this.userInput !== null) {
return this.userInput
} else if (formattedValue) {
return this.type === 'dates'
? formattedValue.join(', ')
: formattedValue
} else {
return ''
}
},
我们可以看到这个值主要与userInput有关,而前面我们看到userInput会在input事件触发后被赋值,所以结论是,页面上展示的值,是由我们点击组件选择日期后直接得到的。
再来看看什么情况下会触发change事件:
pickerVisible(val) {
if (this.readonly || this.pickerDisabled) return
if (val) {
this.showPicker()
this.valueOnOpen = Array.isArray(this.value)
? [...this.value]
: this.value
} else {
this.hidePicker()
this.emitChange(this.value)
this.userInput = null
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.blur')
}
$emit(this, 'blur', this)
this.blur()
}
},
这是watch中监听的一个属性,在选择完日期点击确认后,会关闭picker,让这个值变为false,就会走else分支的代码。
emitChange(val) {
// determine user real change only
if (!valueEquals(val, this.valueOnOpen)) {
$emit(this, 'change', val)
this.valueOnOpen = val
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.change', val)
}
}
}
这是组件主动去触发change事件的方法,它需要先比较传入的val和valueOnOpen这个属性。前面我们看到传入的val就是this.value,在data中可以看到,value由modelValue决定,也就是我们为组件绑定的v-model值。
data() {
return {
value: this.modelValue??this.$attrs.value,
}
},
而在清除数据时,会触发this.emitInput(null)
handleChange() {
if (this.userInput) {
const value = this.parseString(this.displayValue)
if (value) {
this.picker.value = value
if (this.isValidValue(value)) {
this.emitInput(value)
this.userInput = null
}
}
}
if (this.userInput === '') {
this.emitInput(null)
this.emitChange(null)
this.userInput = null
}
},
从这里也能看出,为什么input事件一定会触发,而change有时不触发。
我们发现页面回显只与我们输入的值有关,而change事件与绑定的v-model有关。
所以问题应该出在页面有两个date-picker的问题上,经过实验,发现页面有多个date-picker时,只有第一个会触发change,v-model绑定一样的值也是这样。
总结
当页面上出现两个date-picker时,只有第一个会触发change事件,后面的组件需要监听input事件来获取输入值。这应该与Vue3的新特性有关,具体原因以后我会再分析。