从iView中Select的渲染了解vue的渲染机制

前言

在使用iView的Select的时候,Select组件使用了双向绑定

1
2
3
4
5
6
7
8
9
10
<Select
class="cron_item"
v-model="cronObj.hour"
@on-change="selectedChange"
>
<Option value="*">任意</Option>
<Option v-for="num in 24" :key="num" :value="num - 1 + ''">{{
num - 1
}}</Option>
</Select>

cronObj.hour默认有值假如是*,在mounted的时候我们赋值为5,按道理组件上应该是5的,但是实际上却是*

难道data中的数据的渲染比mounted还晚?

实际上不是的,mounted是在dataprops之后再执行的,那为什么会出现这个问题呢?

我们可以查看一下源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
mounted(){
this.$on('on-select-selected', this.onOptionClick);

// set the initial values if there are any
if (!this.remote && this.selectOptions.length > 0){
this.values = this.getInitialValue().map(value => {
if (typeof value !== 'number' && !value) return null;
return this.getOptionData(value);
}).filter(Boolean);
}

this.checkUpdateStatus();

// remote search, set default-label
if (this.remote && this.value && this.defaultLabel) {
if (!this.multiple) {
this.query = this.defaultLabel;
} else if (this.multiple && (this.defaultLabel instanceof Array) && this.value.length === this.defaultLabel.length) {
const values = this.value.map((item, index) => {
return {
value: item,
label: this.defaultLabel[index]
};
});
this.$emit('on-set-default-options', JSON.parse(JSON.stringify(values)));
setTimeout(() => {
this.values = values;
});
}
}
},

发现iView的Select组件中mounted中赋值是延迟执行的。

这就知道原因了,因为是延迟执行,所以在data渲染的时候,以为渲染过了,mounted回调就开始调用了。

对于两次传入的值,第一次在mounted中触发,后续的都在watch中触发,但是mounted中添加了异步执行,而watch中没有异步调用,所以后续更改的值反倒被之前的值覆盖。

等延迟执行后返回的是之前data的值,mounted设置的值就不生效了。

解决方式

解决方式有以下几种:

使用created

created在渲染之前就覆盖了之前的默认值,这样渲染的时候就是新值了。

1
2
3
4
created() {
console.info("created");
this.getCronArr();
},

使用watch+immediate

默认watch的属性在mounted之前调用,添加immediate: true后,监听函数在创建后就会调用一次,所以会在mounted之前先调用。

建议:

监听props传入值的情况下使用该方式。

示例:

1
2
3
4
5
6
7
8
watch: {
cronStr: {
handler() {
this.getCronArr();
},
immediate: true,
},
},

使用nextTick

Vue会先确保当前的 DOM 更新队列中的所有工作完成,包括组件内部的延迟执行的代码,再执行await this.$nextTick()后的代码。

1
2
3
4
5
async mounted() {
console.info("mounted");
await this.$nextTick();
this.getCronArr();
},

mounted+setTimeout

通过上面的源码我们发现,赋值是延迟执行的,我们再次赋值也添加延迟,就能都放在延迟的队列中,也会等到之前渲染完再执行。

1
2
3
4
5
6
mounted() {
console.info("mounted");
setTimeout(() => {
this.getCronArr();
});
},