问题背景
一开始需求是普通选择时间段,实现如下:
<el-time-picker
is-range
v-model="formData.dutyTime"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
prefix-icon="123"
:clearable="false"
format="HH:mm"
value-format="HH:mm"
style="width: 110px; margin-bottom: 4px;"
align="center"
/>
// formData.dutyTime 输出格式:["00:00", "23:59"]
需求改动:
-
时间段可设置多个;
-
时间段跨天情况(如:22:00-2:00),仅在开始时间大于结束时间时判定为跨天。
根据需求要求,需要改动 dutyTime 属性为时间段数组,并且开始、结束时间分别由两个 el-time-picker 组件控制。修改如下:
<div v-for="(item, index) in formData.dutyTime" :key="index">
<el-time-picker
prefix-icon="123"
:clearable="false"
format="HH:mm"
value-format="HH:mm"
style="width: 110px; margin-bottom: 4px;"
v-model="item[0]"
align="center"
/>
-
<el-time-picker
prefix-icon="123"
:clearable="false"
format="HH:mm"
value-format="HH:mm"
style="width: 110px; margin-bottom: 4px;"
v-model="item[1]"
align="center"
/>
</div>
<script>
export default {
data() {
return {
formData: {}
}
},
},
methods: {
addClick() {
this.formData = {
// dutyTime: ['00:00', '23:59'], // 原需求初始值
dutyTime: [['00:00', '23:59']], // 修改需求后初始值
};
},
}
</script>
一切正常。
因为很多地方都要创建 dutyTime 的对象,所以将 dutyTime 中的对象的创建方式封装。如下:
export class TimeRange {
timeRange = ['00:00', '23:59'];
constructor(startTime = '00:00', endTime = '23:59') {;
this.timeRange[0] = startTime;
this.timeRange[1] = endTime;
}
get startTime() {
return this.timeRange[0];
};
set startTime(time) {
this.timeRange[0] = time;
};
get endTime() {
return this.timeRange[1];
};
set endTime(time) {
this.timeRange[1] = time;
};
}
使用 TimeRange 类创建 dutyTime 对象
addClick() {
this.formData = {
// dutyTime: ['00:00', '23:59'], // 原需求初始值
// dutyTime: [['00:00', '23:59']], // 修改需求后初始值
dutyTime: [new TimeRange()], // 优化后的初始值
};
},
好好好~ 问题来了~
访问器数据的值正确,提交时值也是正确的 dutyTime: "00:00-06:17"。但值没有响应给组价,不能实时显示。

解决目标/预期
实现 class 类创建对象中的访问器属性的响应式。
问题分析
√ 用纯 Js 简单模拟,响应正常:
class TimeRange {
timeRange = ['00:00', '23:59'];
constructor(startTime = '00:00', endTime = '23:59') {
this.timeRange[0] = startTime;
this.timeRange[1] = endTime;
}
get startTime() {
return this.timeRange[0];
};
set startTime(time) {
this.timeRange[0] = time;
};
get endTime() {
return this.timeRange[1];
};
set endTime(time) {
this.timeRange[1] = time;
};
}
let dutyTime = new TimeRange();
// 模拟数据响应式~
Object.defineProperty(dutyTime, "_startTime", {
get() {
return this.startTime;
},
set(time) {
this.startTime = time;
},
});
dutyTime._startTime = "00:12";
console.log(dutyTime._startTime); // 00:12
dutyTime._startTime = "00:52";
console.log(dutyTime._startTime); // 00:52
vue简化模拟问题出现:
<template>
<div class=''>
<div v-for="(item, index) in formData.dutyTime" :key="index">
原始:
<el-time-picker
prefix-icon="123"
:clearable="false"
format="HH:mm"
value-format="HH:mm"
style="width: 110px; margin-bottom: 4px;"
v-model="item.startTime"
align="center"
@change="handlePickerChange"
/>
-
<el-time-picker
prefix-icon="123"
:clearable="false"
format="HH:mm"
value-format="HH:mm"
style="width: 110px; margin-bottom: 4px;"
v-model="item.endTime"
align="center"
@change="handlePickerChange"
/>
{{ formData.dutyTime }}
</div>
<div>
简化:
<el-time-picker
prefix-icon="123"
:clearable="false"
format="HH:mm"
value-format="HH:mm"
style="width: 110px; margin-bottom: 4px;"
v-model="dutyTime.startTime"
align="center"
@change="handlePickerChange"
/>
-
<el-time-picker
prefix-icon="123"
:clearable="false"
format="HH:mm"
value-format="HH:mm"
style="width: 110px; margin-bottom: 4px;"
v-model="dutyTime.endTime"
align="center"
@change="handlePickerChange"
/>
{{ `${dutyTime.startTime}-${dutyTime.endTime}` }}
</div>
{{ count }}
<button @click="handleSubmit" style="display: block;" >submit</button>
</div>
</template>
<script>
import { TimeRange } from './index.js';
export default {
data() {
return {
count: 0,
formData: {},
dutyTime: {},
};
},
methods: {
handlePickerChange(e) {
console.log('e', e);
},
handleSubmit() {
// this.count++;
this.$forceUpdate();
}
},
mounted() {
this.formData = {
dutyTime: [new TimeRange()],
};
this.dutyTime = new TimeRange();
console.log('dutyTime', this.dutyTime);
},
}
</script>
在页面上直接展示 dutyTime 对象,没有反应,证明与数据双向绑定指令 v-model 无关:

执行强制刷新: this.count++ 或 this.$forceUpdate(),界面数据刷新,正确:

说明:数据变化没有被监听到。还是响应式的问题!!!!
查看Vue2.6.11响应式实现源码:vue\src\core\observer\index.ts ---> class Observer

Object.keys():静态方法返回一个由给定对象自身的(不包含原型链上的)可枚举的字符串键属性名组成的数组。
所以问题的根本原因还是 字面量 和 class 创建对象的方式不同,导致属性 'startTime', 'endTime' 缺失:
-
字面量创建对象的形式访问器属性是在自身对象上的
-
class类创建对象的形式访问器属性是在原型对象上的
class TimeRange {
timeRange = ['00:00', '23:59'];
constructor(startTime = '00:00', endTime = '23:59') {
this.timeRange[0] = startTime;
this.timeRange[1] = endTime;
}
get startTime() {
return this.timeRange[0];
};
set startTime(time) {
this.timeRange[0] = time;
};
get endTime() {
return this.timeRange[1];
};
set endTime(time) {
this.timeRange[1] = time;
};
}
let dutyTime = new TimeRange();
console.log(Object.keys(dutyTime)); // [ 'timeRange' ]
解决方式
1.class 创建直接返回一个字面量对象
class TimeRange {
constructor(startTime = '00:00', endTime = '23:59') {;
// 直接返回字面量对象 方式 √
return {
timeRange: [startTime, endTime],
get startTime() {
return this.timeRange[0];
},
set startTime(time) {
this.timeRange[0] = time;
},
get endTime() {
return this.timeRange[1];
},
set endTime(time) {
this.timeRange[1] = time;
},
}
}
}
let dutyTime = new TimeRange();
console.log(Object.keys(dutyTime)); // [ 'timeRange', 'startTime', 'endTime' ]
2.使用 Object.defineProperty 给实例对象上添加访问器属性
export class TimeRange {
timeRange = ['00:00', '23:59'];
constructor(startTime = '00:00', endTime = '23:59') {
this.init();
this.startTime = startTime;
this.endTime = endTime;
}
init() {
Object.defineProperty(this, "startTime", {
get: function () {
return this.timeRange[0];
},
set: function (time) {
this.timeRange[0] = time;
},
enumerable: true, // 默认值为 false(不可遍历)
configurable: true, // 默认值为 false(不可更改属性配置)
});
Object.defineProperty(this, "endTime", {
get: function () {
return this.timeRange[1];
},
set: function (time) {
this.timeRange[1] = time;
},
enumerable: true, // 默认值为 false(不可遍历)
configurable: true, // 默认值为 false(不可更改属性配置)
});
}
}
let dutyTime = new TimeRange("12:12");
console.log(Object.keys(dutyTime)); // [ 'timeRange', 'startTime', 'endTime' ]
除了让访问器属性可遍历(enumerable),需要可修改配置(configurable),因为:

一切正常 over~

2095

被折叠的 条评论
为什么被折叠?



