暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

VUE中watch踩坑记

Geeker工作坊 2021-08-06
1571

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的三种用法,然后列举了我自己在开发过程当中遇到的问题,然后提出了改进的办法。开发的过程中要多积累、多总结,希望与大家共同进步!


                    文章转载自Geeker工作坊,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                    评论