《Vue-进阶属性之watch》

282 阅读3分钟

watch:监听/侦听

当监听到数据变化时,执行一个函数

语法

类型:{ [key: string]: string | Function | Object | Array }

watch是一个对象,里边是键值对的形式。值可以是字符串,函数,对象,数组

watch:{
    //函数,val和oldVal是Vue自动传进去的
    a:function(val,oldVal){},
    //ES6新语法,可以把:function省略
    b(){},
    //字符串,方法名,在methods里定义
    c:'methodName',
    //对象
    d:{
        handler(){},    //数据变化时执行的操作
        deep:true,      //如果监听的是对象,往深了看
        immediate:true  //第一次渲染就触发watch
    },
    //数组,e变化,依次执行数组里的函数
    e:[f1,f2,f3],
    //监听一个对象里的属性,最好不要省掉:function,有的webpack的版本会报错
    'obj.a':function(){}
}
vm.$watch('n',function(){
    console.log('n变了')
},{deep:... , immediate:...})

例1:撤销

new Vue({
  data: {
    n: 0,
    history: [],
    inUndoMode: false
  },
  watch: {
    n(newVal,oldVal){
      console.log(this.inUndoMode)
      if(!this.inUndoMode){
        this.history.push({from:oldVal,to:newVal})
      }
    }
  },
  template: `
    <div>
      {{n}}
      <hr />
      <button @click="add1">+1</button>
      <button @click="add2">+2</button>
      <button @click="minus1">-1</button>
      <button @click="minus2">-2</button>
      <hr/>
      <button @click="undo">撤销</button>
      <hr/>
      {{history}}
    </div>
  `,
  methods: {
    add1() {
      this.n += 1;
    },
    add2() {
      this.n += 2;
    },
    minus1() {
      this.n -= 1;
    },
    minus2() {
      this.n -= 2;
    },
    undo() {
     const last=this.history.pop()
     const old=last.from
     this.inUndoMode=true
     this.n=old
     //this.inUndoMode=false   watch是异步的
     this.$nextTick=(()=>{
      this.inUndoMode=false
     },0)
    }
  }
}).$mount("#app");

div里,分别是数字n ,四个按钮(点击做相应的加减操作)。history数组,用于保存历史记录。每次n变化时,把从几变到几以对象形式push进数组。

所以用watch监听,当n变化时,执行相应函数。n()函数接收两个参数,新值和旧值。

添加撤销按钮。点击,就会把数字变成上一步的值,同时把history数组的最后一项删掉(undo函数里)。

但是有一个bug:我们通过this.n=old把n改成上一步的值,但是这也是对n的改变,watch就会监听到n的变化,就会又把这次变化push进数组。所以我们希望当点击撤销按钮时,不让watch监听。

解决方法:因为watch的要点就是监听数据的变化,并且触发函数。所以可以使用一个变量,来判断是否该触发watch函数。

我们添加一个this.inUndoMode变量,表示是否处于撤销模式。默认是false。点击撤销按钮时,改为true。所以watch里先判断,this.inUndoMode是false时,才push。然后在undo()点击函数里,修改this.n之前,把它改为true,表示现在是撤销模式了,watch不要操作了。然后修改完n之后,再改为false.

但是发现,这样也有bug。通过在watch里打log发现,点击撤销按钮,this.inUndoMode依然是false。

这是因为watch是异步的。 this.n=old 之后,会触发watch,但是watch并不会马上执行,而是先执行完后面代码,也就是this.inUndoMode=false,再去执行watch。所以当执行watch的时候,已经又变回false了,就又push进页面了。

解决办法:既然watch异步,那我把后边的代码也放在异步里,等watch执行完,再执行改回false的操作。因为watch里的异步接口是使用$nextTick实现的,所以也使用这个,属于同级异步。所以改变n在前,watch会先被触发,也就会先去执行。

例2:模拟computed

什么是变化?

我们说watch是当数据变化时,会触发函数。那什么是变化呢?

 data: {
    n: 0,
    obj: {
      a: "a"
    }
},
template: `
    <div>
      <button @click="n += 1">n+1</button>
      <button @click="obj.a += 'hi'">obj.a + 'hi'</button>
      <button @click="obj = {a:'a'}">obj = 新对象</button>
    </div>
`,
  watch: {
    n() {
      console.log("n 变了");
    },
    obj() {
      console.log("obj 变了");
    },
    "obj.a": function() {
      console.log("obj.a 变了");
    }
}

为了检验,可以添加三个按钮。可以用watch监听n,obj,obj.a,如果变化了,就会打印相应的话。

(嵌套属性在watch里,写成:'obj.a',可以把:function省掉)

修改n,n当然变了

修改obj.a,obj.a变了,obj没变

修改obj,虽然视觉上看起来和原来长得一样,但是地址变了,所以obj变了。但是obj.a没变,因为obj.a的值还是'a'

简单类型看值,复杂类型(对象)看地址

deep选项

由上边可以知道,obj对象里边的属性变了,obj是不变的,因为它的地址没变。

但是有时候的需求是,只要obj对象里的属性变了,就算obj变了。

可以使用deep选项,比如监听obj的变化,希望它里边的属性变化也会触发它的执行函数:

 watch: {
    n() {
      console.log("n 变了");
    },
    obj:{
        handler(){
            console.log("obj 变了");
        },
        deep:true
    }
}

要监听的obj写成对象形式,把数据变化要做的事放在handler函数里。deep属性写true。

deep的意思就是监听obj的时候,是否往深了看,看不看它里边的属性。默认是false。

面试题: computed和watch的区别

  1. 翻译
  2. 各自描述,可用代码举例

computed是计算属性,watch是监听的意思。

computed会根据它依赖的属性计算出属性。它调用的时候不加括号,直接当作属性使用。计算属性会被自动缓存,如果它依赖的属性不变,就不会重新计算。

watch是当监听的数据变化时,执行一个函数。它有两个选项,deep是当监听一个对象的时候,是否要看这个对象里边的属性的变化。immediate表示是否在第一次渲染的时候就执行这个函数。

总结来说就是,如果一个数据依赖于其他数据,那么把这个数据设计为computed的

如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化