VUE中watch踩坑记
开始之前
作为一名VUE的开发者,一直使用watch来响应数据的变化,最近做了几个项目遇到了很多问题,突然感觉自己有点滥用watch了。本次主要想和大家分享一下我在做项目过程中因为滥用watch踩到的坑,算一次自我反省总结。关注我的朋友们也可以对照一下看看有没有同样的问题,大家共同进步!
本次文章共分两部分
watch用法回顾
我的踩坑记录
watch 用法回顾
在VUE中,使用watch来响应数据的变化。watch的用法大致有三种。
1. 简单用法
直接写一个监听处理函数,当每次监听到 text值发生改变时,执行函数。
<input type="text" v-model="cityName"/>
new Vue({
el: '#root',
data: {
text: 'shanghai'
},
watch: {
text(newText, oldText) {
// ...
}
}
})
也可以在所监听的数据后面直接加字符串形式的方法名:
watch: {
text: 'textChange'
}
2. immediate 和 handler
第一种方法使用watch时有一个特点,就是当值第一次绑定时(data中直接赋值),不会执行监听函数,只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate
属性。
比如当父组件向子组件动态传值时,子组件props首次获取到父组件传来的默认值时,也需要执行函数,此时就需要将immediate设为true。
export default {
name: "HelloWorld",
props:{
text:{
type: String,
required: true,
}
},
data() {
return {
childText: this.text,
}
},
watch: {
childText:{
handler(newVal){
console.log("watch watch watch", newVal)
},
immediate: true
}
}
}
监听的数据后面写成对象形式,包含handler方法和immediate,之前我们写的函数其实就是在写这个handler方法;immediate表示在watch中首次绑定的时候,是否执行handler,值为true则表示在watch中声明的时候,就立即执行handler方法,值为false,则和一般使用watch一样,在数据发生变化的时候才执行handler。
3. deep
当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。
export default {
name: "VueDeep",
data(){
return {
city: { id: "123", name: "北京", address: "北京西城"}
}
},
created() {
let timer = setTimeout(()=>{
this.city.id = "456";
clearTimeout(timer)
},2000)
},
watch:{
city: {
handler(newVal){
console.log("watch watch watch", newVal)
},
deep: true
}
}
}
设置deep: true 则可以监听到city.id的变化,此时会给city的所有属性都加上这个监听器,当对象属性较多时,每个属性值的变化都会执行handler。如果只需要监听对象中的一个属性值,则可以做以下优化:使用字符串的形式监听对象属性:
watch:{
"city.id":{
handler(newVal){
console.log("watch watch watch", newVal)
},
}
}
我的踩坑记录
1. 错误理解“当值第一次绑定时(data中直接赋值),不会执行监听函数”
发现问题的场景是有一个用户表单组件,用户分为不同类型,我使用watch监听了用户类型字段,如果发生变化则进行一次表单清空。由于用户的新增和编辑都使用同一个组件,所以用户类型会有一个默认值,创建时没有问题,但修改时由于修改的用户类型与默认用户类型不同,赋值时就触发了watch事件,导致表单内容被清空。场景代码简化如下:
export default {
name: "HelloWorld",
props:{
text:{
type: String,
required: true,
}
},
data() {
return {
childText: "",
}
},
created() {
this.childText = this.text;
},
watch: {
childText:{
handler(newVal){
console.log("watch watch watch", newVal)
},
}
}
}
childText初始默认值为空,当组件创建时,将父组件传来text的值赋给子组件childText。在create中的赋值其实是会触发watch函数的。所以要牢记第一次赋值时不会触发,一定要是在data中将父组件传进来的值赋给子组件!!!如果有这种需求,比如用户类型是一个是使用el-select
下拉选择框,则可以单独写一个change事件,进行表单的清空。
2. 巧妙避免使用watch属性
项目中使用element组件库中的el-dialog做了组织机构的新增/编辑表单,表单中有一个选项是上级节点
,该上级节点列表是当前时间的组织机构列表。因为组织机构随时可以新增,而el-dialog在实例化后就不在执行create声明周期的钩子函数来获取组织机构列表,所以可以使用watch属性做一次visible属性的监听,在visible属性为true的时候做一次数据请求。场景简化代码如下:
<template>
<el-dialog
title="编辑"
:close-on-click-modal="false"
:visible="dialogVisible"
@close="onClose"
destroy-on-close
>
<span>这是一段信息</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
export default {
name: "EditDialog",
props: {
show: {type: Boolean, default: false},
},
data() {
return {
dialogVisible: false,
};
},
created() {
this.dialogVisible = this.show;
this.getOrg();
},
watch: {
show(val) {
this.dialogVisible = val;
},
},
methods: {
getOrg(){
let timer = setTimeout(()=>{
console.log("time-out time-out time-out");
clearTimeout(timer);
},1000)
},
onClose() {
this.dialogVisible = false;
this.$emit("close");
},
},
};
</script>
以上代码 GetOrg只会执行一次,只有按照如下方式修改才能再弹出框打开是调用GetOrg。
watch: {
show(val) {
this.dialogVisible = val;
if(val){
this.getOrg()
}
},
},
我们也可以使用另一种办法来实现该方法
<EditDialog v-if="show" :show="show" @close="show=false"></EditDialog>
可到区别了吗?原来的调用时候没有v-if
,现在加上后就可以打到效果了。至于为什么,感兴趣的同学可以查找一下v-if
的原理。
总结
本文首先回顾了VUE中watch的三种用法,然后列举了我自己在开发过程当中遇到的问题,然后提出了改进的办法。开发的过程中要多积累、多总结,希望与大家共同进步!