Vue: computed和watch

回顾一下响应式原理

完整文章: Vue: 深入理解数据响应式 | 修

options.data

  • 会被Vue监听
  • 会被Vue示例代理
  • 每次对data的读写都会被Vue监控
  • Vue会在data变化时更新UI

本篇文章讲

  • data变化时除了更新UI,还能做些什么?

computed - 计算属性

用途

  • 被计算出来的属性就是计算属性

例子一:

main.js

// 引用完整版 Vue,方便说明
import Vue from "vue/dist/vue.js";

new Vue({
  data: {
    user: {
      email: "hahaha123@gmail.com",
      nickname: "修",
      phone: "18832388888",
    },
  },
  computed: {
    displayName: {
      get() {
        const user = this.user;
        return user.nickname || user.email || user.phone;
      },
      set(value) {
        console.log(value);
        this.user.nickname = value;
      },
    },
  },
  // 不如用 computed 来计算 displayName
  template: `
      <div>
      {{displayName}}
      <button @click="add">set</button>
      </div>
  `,
  methods: {
    add() {
      this.displayName = "大白";
    },
  },
}).$mount("#app");

例子二:

import Vue from "vue/dist/vue.js";

Vue.config.productionTip = false;
let id = 0;
const createUser = (name, gender) => {
  id += 1;
  return { id: id, name: name, gender: gender };
};
new Vue({
  data() {
    return {
      users: [
        createUser("Alice", "Female"),
        createUser("Bob", "Male"),
        createUser("Charlie", "Male"),
        createUser("Dupon", "Female")
      ],
      displayUsers: []
    };
  },
  created() {
    this.displayUsers = this.users;
  },
  methods: {
    showAll() {
      this.displayUsers = this.users;
    },
    showMale() {
      this.displayUsers = this.users.filter(u => u.gender === "Male");
    },
    showFemale() {
      this.displayUsers = this.users.filter(u => u.gender === "Female");
    }
  },
  template: `
    <div>
      <div>
      <button @click="showAll">All</button>
      <button @click="showMale">Male</button>
      <button @click="showFemale">Female</button>
      </div>
      <ul>
        <li v-for="(u, index) in displayUsers" :key="index">{{u.name}} - {{u.gender}}</li>
      </ul>
    </div>
  `
}).$mount(“#app”);
import Vue from "vue/dist/vue.js";

Vue.config.productionTip = false;
let id = 0;
const createUser = (name, gender) => {
  id += 1;
  return { id: id, name: name, gender: gender };
};
new Vue({
  data() {
    return {
      users: [
        createUser("Alice", "Female"),
        createUser("Bob", "Male"),
        createUser("Charlie", "Male"),
        createUser("Dupon", "Female"),
      ],
      gender: "",
    };
  },
  computed: {
    displayUsers() {
      const hash = {
        Male: "Male",
        Female: "Female",
      };
      const { users, gender } = this;
      if (gender === "") {
        return users;
      } else if (typeof gender === "string") {
        return users.filter((u) => u.gender === hash[gender]);
      } else {
        throw new Error("Unexpected gender!");
      }
    },
  },
  methods: {
    setGender(string) {
      this.gender = string;
    },
    // showAll() {
    //   this.gender = "";
    // },
    // showMale() {
    //   this.gender = "Male";
    // },
    // showFemale() {
    //   this.gender = "Female";
    // }
  },
  template: `
    <div>
      <div>
      <button @click="setGender('')">All</button>
      <button @click="setGender('Male')">Male</button>
      <button @click="setGender('Female')">Female</button>
      </div>
      <ul>
        <li v-for="(u, index) in displayUsers" :key="index">{{u.name}} - {{u.gender}}</li>
      </ul>
    </div>
  `,
}).$mount("#app");

缓存

  • 如果依赖的属性没有变化,就不会重新计算
  • getter / setter默认不会做缓存,Vue做了特殊处理
  • 如何缓存? 看示例

这只是一种思路,不代表Vue这样实现

watch - 监听 / 侦听

用途

  • 当数据变化时,执行一个函数

两个例子

import Vue from "vue/dist/vue.js";

Vue.config.productionTip = false;

new Vue({
  data: {
    n: 0,
    history: [],
    // 是否处于撤销模式
    inUndoMode: false
  },
  watch: {
    n(newValue, oldValue) {
      if (!this.inUndoMode) {
        this.history.push({ from: oldValue, to: newValue });
      }
    }
  },
  // 不如用 computed 来计算 displayName
  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();
      // 不能这么做,因为撤销的操作也会被watch到
      // const old = last.from;
      // this.n = old;
      // 那能不能不watch data changes?
      // Evan: just do a conditional check inside the watcher callback

      // ok,我继续试试
      // const old = last.from;
      // this.inUndoMode = true;
      // this.n = old; // watch是异步的
      // this.inUndoMode = false;
      // 这个时候才会检查inUndoMode,那肯定它一直都是false
      // 那怎么办?

      // 让子弹飞一会,等watch执行完了,你再变成false
      const old = last.from;
      this.inUndoMode = true;
      this.n = old; // watch是异步的
      this.$nextTick(() => {// 精髓之笔
        this.inUndoMode = false;
      }, 0);
    }
  }
}).$mount(“#app”);

附录:Ability to not trigger watch handler on data? | Evan You:

The point of a watcher is that it will fire when the data changes. Instead of thinking about stopping it from firing, just do a conditional check inside the watcher callback.

通过这个例子,发现watch是完美实现历史功能的一个方法。

说实话,这样做很傻

这个例子告诉我们,既可以用computed也可以用watch的话,就先用computed,不行再用watch。他们都是在数据变化的时候去执行一个函数,只不过computed主要着重于依赖之间的变化以及缓存,watch主要着重于变化的时候去执行一个什么东西,而不是得出一个结果(也可能什么结果都没有)。比如我watch的时候,我就打一个log,那你用computed就不合适吧,或者说watch的时候我就记录一个历史,那你用computed也不合适吧。因为此时我并不需要你得出一个属性结果出来,我只需要你去执行一个函数,去操作一个内部数据而已。

何为变化?

对于watch或是对于vue的所有监听器来说,什么是变化?

Q: obj原本是{a: ‘a’},现在obj = {a: 'a'},请问obj变了没有?obj.a变了没有?

A: obj没有变, obj.a变了。因为obj是对象且地址没变,obj.a是简单类型而值变了。

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

这其实就是===的规则

watch 的 deep 选项

Q: 如果obj.a变了,请问obj算不算也变了?

A: 如果你需要的答案是"也变了",那么就用deep:true,如果你需要的答案是"没有变",那么就用deep:false

deep的意思是,监听object的时候是否往深了看

import Vue from "vue/dist/vue.js";

new Vue({
  data: {
    obj: {
      a: "a"
    }
  }
  watch: {
    obj: {
      deep: true
    }
  }
}).$mount(“#app”);

自己试试

watch 的完整语法

watch的功能已经说完了,现在聊聊watch的完整语法

  • 语法一:

官方文档: watch — Vue.js

watch: {
  o1: () => {}, // 别用这种,这里的this是全局对象

  o2: function(value, oldValue){},

  o3(){},

  o4:[f1,f2],  // o4变化的时候依次执行f1, f2

  o5:'methodName'

  o6: {handler:fn, deep :true, immediate:true},

	'object.a' : function(){}
}
  • 语法二:
vm.$watch('xxx', fn, {deep:...;immediate:...})
// 其中'xxx’可以改为一个返回字符串的函数

// 上面的写法很丑,不如挂在生命周期钩子里
created(){
    this.$watch('xxx', fn, {deep:...;immediate:...})
}

computed 和 watch 的区别

Q: 前面讲了computedwatch,现在灵魂一问来了,computedwatch的区别是什么?

A: 好吧,那谈谈我的想法

  • computed是计算属性,watch是监听
  • computed是用来计算出一个值的,这个值在调用的时候,第一个是不需要加括号,可以当属性这样用,第二个是根据依赖会自动缓存,如果依赖不变,这个computed的值就不会重新计算
  • watch是用来监听的,如果某个属性变化了就去执行一个函数。watch有两个选项,第一个选项是immediate,表示在第一次渲染的时候是否执行这个函数,第二个选项是deep,表示如果我们监听一个对象,那么我们是否要看这个对象里面的属性的变化
comments powered by Disqus