Vue(二):侦听器watch和计算属性computer

1,361 阅读2分钟

  这个知识点经常问,本来我是想把这个放在一个大类里面说的,但是发现越写越多,就干脆单独拿出来用一篇来写了,在这里我就总结一下我个人对这两个知识点的理解。其中参考了这位大佬的文章《vue watch原理》,不胜感激。

侦听器watch(一对多)

image.png

  • 作用

    简而言之就是监听数据的变化,可以通过监听某个数据的变化来进行其他操作。也就是 一对多(一个数据影响多个数据) 的关系。

  • 常用属性

    • immediate:在选项参数中指定immediate: true会在初始化watch时就立即执行handler回调函数,而不是等下一次数据更新。注意在带有immediate选项时,你不能在第一次回调时取消侦听给定的property
    export default {
        data() {
            return {
                foo: "123",
            };
        },
        watch: {
            foo: {
                handler(newData, oldData) {
                    console.log("newData:", newData); //123
                    console.log("oldData:", oldData); //undefined
                },
                immediate: true,//这个属性设置为true以后就会立马触发监听事件显示打印,如果是false,则此时不会有打印数据,要等到修改foo才会触发监听事件。
            },
        },
    };
    
    • deep:为了发现内部值的变化可以设置deep: true,监听数组的变更则不需要这么做,因为数组用this.$set方法是可以直接检测到的。但是如果把数组对象的某个元素的其中一个属性直接替换,还是要加deep: true
    export default {
        data() {
            return {
                foo: {
                    name: "yyy",
                    age: "12",
                },
            };
        },
        watch: {
            foo: {
                handler(newData, oldData) {
                    console.log("newData:", newData.age);
                    console.log("oldData:", oldData.age);
                },
                deep: true,//如果deep值不为true,不会触发handler事件
            },
        },
        mounted() {
            this.foo.age = "321";
        },
    };
    
  • 用法

    • 监听基础数据类型

      export default {
          data() {
              return {
                  foo: "123",
              };
          },
          watch: {
              foo(newData, oldData) {
                  console.log("newData:", newData); //321
                  console.log("oldData:", oldData); //123
              },
          },
          mounted() {
              this.foo = "321";
          },
      };
      
    • 监听对象

      • 监听整个对象
        • 替换和变更的结果是不一样的,如上图所言,变更的前后值是相同的,替换则能拿到旧值。
      export default {
          data() {
              return {
                  foo: {
                      name: "xxx",
                      id: "123",
                      class: "一",
                  },
                  bar: {
                      name: "xxx",
                      id: "123",
                      class: "一",
                  },
              };
          },
          watch: {
              foo: {
                  handler(newData, oldData) {
                      console.log("newData:", newData.name); //yyy
                      console.log("oldData:", oldData.name); //yyy
                  },
                  deep: true,
              },
              bar: {
                  handler(newData, oldData) {
                      console.log("newData:", newData.name); //yyy
                      console.log("oldData:", oldData.name); //xxx
                  },
                  deep: true,
              },
          },
          mounted() {
              this.foo.name = "yyy";
              this.bar = {
                  name: "yyy",
                  id: "321",
                  class: "一二",
              };
          },
      };
      
      • 监听单个属性
      export default {
          data() {
              return {
                  foo: {
                      name: "xxx",
                      id: "123",
                      class: "一",
                  },
              };
          },
          watch: {
              'foo.name'(newData, oldData) {
                  console.log("newData:", newData); //yyy
                  console.log("oldData:", oldData); //xxx
              },
          },
          mounted() {
              //只触发一次监听
              this.foo.name = "yyy";
              setTimeout(() => {
                  this.foo.id = "321";
              }, 1000);
          },
      };
      
    • 监听数组

      • 由于新旧数据是相同的,可以借用计算属性深拷贝可以避免这种情况。监听对象也是同理。
      export default {
          data() {
              return {
                  foo: [1, 2, 3, 4, 5],
              };
          },
          computed: {
              bar() {
                  return JSON.parse(JSON.stringify(this.foo));
              },
          },
          watch: {
              foo(newData, oldData) {
                  console.log("newData:", newData); //[1, 22, 3, 4, 5]
                  console.log("oldData:", oldData); //[1, 22, 3, 4, 5]
              },
              bar(newData, oldData) {
                  console.log("newData:", newData); //[1, 22, 3, 4, 5]
                  console.log("oldData:", oldData); //[1, 2, 3, 4, 5]
              },
          },
          mounted() {
              this.$set(this.foo, 1, 22);
          },
      };
      
  • 特点

    • watch允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。计算属性会立即返回,而此时异步操作可能还没有完成;
    • 如果前后两个值相同,不会触发侦听器
    export default {
        data() {
            return {
                foo: "123",
            };
        },
        watch: {
            foo(val) {
                setTimeout(() => {
                    console.log("foo-data", val); //可以触发
                }, 1000);
            },
            bar(val) {
                console.log("bar-data", val); //无法触发
            },
        },
        computed: {
            bar() {
                setTimeout(() => {
                    return this.foo; //计算属性会立刻返回值并不能做其他过多操作,如果不用setTimeout方法是可以触发上面bar的watch监听的
                }, 1000);
            },
        },
        mounted() {
            this.foo = "321";
        },
    };
    

计算属性computer(多对一)

  • 作用

    通俗来说就是一个值或多个值拼接或者组合而成的一个值,参考变形金刚里的合体金刚大力神(手动狗头)。而且computer中的数据不需要在data中再次声明

  • 用法

    export default {
        data() {
            return {
                foo: "123",
                bar: "321"
            };
        },
        computed: {
            zoo() {
                return this.foo + this.bar; //zoo不需要在data中定义,直接用this.zoo调用就可以
            },
        }
    };
  • 特点

    计算属性是基于它们的响应式依赖来进行缓存的,只有在相关的依赖发生变化的时候才会再次求值计算。这样可以节省想当多的性能,不管是用侦听器还是用函数来达到同样的效果,性能都不如计算属性。