4 Vue2的构造属性 -- computed 和 watch 属性

278 阅读2分钟

计算属性

计算属性API计算属性教程
计算属性在template中可以像对象的属性一样使用

import Vue from "vue/dist/vue"
Vue.config.productionTip = false;
new Vue({
  data: {
    user: {
      email: "ds@163.com",
      nickName: "xyz",
      phone: "187",
    }
  },
  computed: {
    // 计算属性第一种写法,计算属性的值是一个函数
    userInfos() {
      const user = this.user;
      return user.nickName || user.email || user.phone;
    },
    // 计算属性的第二种写法,计算属性的值是一个对象,对象包括get和set方法
    displayName: {
      get() {
        const user = this.user;
        return user.nickName || user.email || user.phone;
      },
      set(value) {
        this.user.nickName = value;
      }
    }
  },
  methods:{
    setNickName(){
      console.log("setNickName")
      this.displayName = "dswxyz"; // 计算属性的赋值
    }
  },
  template: `
    <div>
    {{ userInfos }}
    <hr>
    {{ displayName }} &nbsp; <button @click="setNickName">setNickName</button>
    </div>
  `
}).$mount('#app');

注意,如果依赖(computed中基础属性)没有变,computed会使用缓存的计算结果;

计算属性,数组计算与展示

import Vue from "vue/dist/vue"
Vue.config.productionTip = false;
let index = 0;
const createUser = (name, gender) => {
  index += 1;
  return {id: index, name: name, gender: gender};
}
new Vue({
  data: {
    users: [
      createUser("王重阳", "男"),
      createUser("祖冲之", "男"),
      createUser("小龙女", "女"),
      createUser("张曼玉", "女"),
    ],
    gender: "all",
  },
  computed: {
    displayUsers() {
      const hash = {"all":"","male":"男","female":"女"}
      return this.users.filter((u) => this.gender === "all" || u.gender === hash[this.gender]);
    }
  },
  template: `
    <div>
    <div>
      <button @click="gender = 'all'">全部</button>
      <button @click="gender = 'male'">男</button>
      <button @click="gender = 'female'">女</button>
    </div>
    <hr/>
    <ul>
      <li v-for="(u,index) in displayUsers" :key="index">{{ u.name }} {{ u.gender }}</li>
    </ul>
    </div>
  `,
}).$mount('#app');

其中,displayUsers是对数组数据的计算

watch属性

watch API
watch能够监听到数据的变化,比如数据n发生变化时,一定会被watch监听,监听方法中,可以进行相关数据处理或者操作,如果不想操作,可以设置flag规避;
注意,watch的执行是异步的,在改变n的操作后,直接重置flag是无效的

this.inUndoMode = true;
this.n = last.from; // watch 是异步的
this.inUndoMode = false; // 在异步的watch执行完再执行

上面重置flag(inUndoMode)的操作是无效的,必须用链式操作或者nextTick:

import Vue from "vue/dist/vue";
Vue.config.productionTip = false;
new Vue({
  data: {
    n: 0,
    inUndoMode: false,
    history: []
  },
  watch: {
    n(newValue, oldValue) {
      !this.inUndoMode && this.history.push({from: oldValue, to: newValue});
    }
  },
  template: `
    <div>
    {{ n }}
    <hr>
    <button @click="n += 1">+1</button>
    <button @click="n += 2">+2</button>
    <button @click="n -= 1">-1</button>
    <button @click="n -= 2">-2</button>
    <hr>
    <button @click="undo">撤销</button>
    <hr>
    {{ history }}
    </div>`,
  methods: {
    undo() {
      const last = this.history.pop();
      this.inUndoMode = true;
      // this.n = last.from; // watch 是异步的
      // this.inUndoMode = false; // 无效,因为watch是异步操作的
      // 规避异步watch的方法:使用链式操作
      Promise.resolve().then(() => {
        this.n = last.from;
      }).then(() => {
        this.inUndoMode = false
      });
      // 规避异步watch的方法:使用nextTick,下一次能动的时候执行
      this.n = last.from;
      this.$nextTick(() => {
        this.inUndoMode = false;
      });
    }
  }
}).$mount('#app');

watch的immediate选项

immediate选项,表示监听数据从无到有的变化,下面是模拟computed的效果

import Vue from "vue/dist/vue"
Vue.config.productionTip = false;
new Vue({
  data() {
    return {
      user: {
        nickName: "dsw",
        phone: "187",
        email: "ds@163.com"
      },
      displayName: undefined,
    };
  },
  watch:{
    "user.nickName":{
      handler(){
        const {user: {nickName, email, phone}} = this;
        this.displayName = nickName || email || phone;
      },
      immediate: true, // 表示监听从无到有的变化
    },
    "user.email":{
      handler(){
        const {user: {nickName, email, phone}} = this;
        this.displayName = nickName || email || phone;
      },
      immediate: true,
    },
    "user.phone":{
      handler(){
        const {user: {nickName, email, phone}} = this;
        this.displayName = nickName || email || phone;
      },
      immediate: true,
    },
  },
  template: `
    <div>
    {{ displayName }}
    <hr>
    <button @click="user.nickName = ''">RemoveName</button>
    </div>`,
}).$mount("#app");

watch的deep选项,监听对象属性的变化

正常情况下,watch只能监听基础数据类型值的变化,或者对象类型地址的变化,要监听对象属性值的变化,需要加上deep选项

import Vue from "vue/dist/vue"
Vue.config.productionTip = false;
new Vue({
  data() {
    return {
      n: 0,
      obj: { name: "dsw" },
      user: {name : "dsw"},
    }
  },
  template:`<div>
    {{n}} <button @click="n += 1">n+1</button> // watch: n 变化了
    <hr>
    {{obj}} <button @click="obj = {name: 'dsw'}"> obj new</button> // watch: obj 变化了
    <hr>
    {{obj}} <button @click="obj.name = 'xyz'"> obj.name = xyz</button> // watch: obj.name 变化了
    <hr>
    {{user}} <button @click="user.name = 'xyz'"> user.name = xyz</button> // watch: user.name 变化了, watch: user 变化了
  </div>`,
  watch:{
    n(){ console.log("watch: n 变化了"); },
    "obj.name"(){ console.log("watch: obj.name 变化了"); },
    obj(){ console.log("watch: obj 变化了"); },
    "user.name"(){ console.log("watch: user.name 变化了"); },
    user:{
      handler(){ console.log("watch: user 变化了") },
      deep: true,
    }
  }
}).$mount("#app");

代码中,user对象的监听加了deep选项,能监听到name的变化,但是obj对象就监听不到name的变化

watch的另一种用法

created() {
  this.$watch('n',function (){
    console.log("n change")
  },{immediate: true})
},

watch的语法总结

watch:{
  obj: function (){  }, 
  obj(){ },
  obj: ["onObjChanged", "onObjChanged2"],
  obj: "onObjChanged",
  obj:{
    handler(){},
    deep: true,
    immediate: true,
  },
},

其中,onObjChangedonObjChanged2是在methods中定义的函数;