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
说实话,这样做很傻
这个例子告诉我们,既可以用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: 前面讲了computed和watch,现在灵魂一问来了,computed和watch的区别是什么?
A: 好吧,那谈谈我的想法
- computed是计算属性,watch是监听
- computed是用来计算出一个值的,这个值在调用的时候,第一个是不需要加括号,可以当属性这样用,第二个是根据依赖会自动缓存,如果依赖不变,这个computed的值就不会重新计算
- watch是用来监听的,如果某个属性变化了就去执行一个函数。watch有两个选项,第一个选项是immediate,表示在第一次渲染的时候是否执行这个函数,第二个选项是deep,表示如果我们监听一个对象,那么我们是否要看这个对象里面的属性的变化