Computed(计算属性)
类型
{ [key: string]: Function | { get: Function, set: Function } }
计算属性将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例。
使用
我们很多人喜欢在模板上直接把想要的结果通过运算呈现出来,比如:
//直接实现字符串的翻转
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
new Vue({
data :{
message:"Hello"
},
template: `
<div>
newMessage:{{message.split('').reverse().join('')}}
</div>
`
}).$mount("#app");
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护,当你想要在模板中多包含此处的翻转字符串时,就会更加难以处理。所以,对于任何复杂逻辑,你都应当使用计算属性。
代码如下:
//使用计算属性实现字符串翻转
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
const vm=new Vue({
data :{
message:"Hello"
},
computed:{
//计算属性的getter
reversedMessage(){
return this.message.split('').reverse().join('')
}
},
template: `
<div>
newMessage:{{reversedMessage}}
</div>
`
}).$mount("#app");
这里我们申明了一个计算属性reversedMessage,我们提供的函数用作propertyreversedMessage的getter函数:
console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'
Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。
计算属性VS方法
上面的效果我们也可以用方法来实现:
//通过方法来实现字符串翻转
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
new Vue({
data :{
message:"Hello"
},
methods:{
reversedMessage(){
return this.message.split('').reverse().join('')
}
},
template: `
<div>
newMessage:{{reversedMessage()}}
</div>
`
}).$mount("#app");
他们得到的函数是一样的,结果也是一样的,执行就是相差一个( )而已,然而他们最大的区别就是计算属性基于它的响应式依赖进行缓存的,只在相关响应式依赖发生改变时才会重新求值,这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。
计算属性的 setter
计算属性默认只有一个getter,但是可以添加一个setter:
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新。
watch(侦听属性)
类型
{ [key: string]: string | Function | Object | Array }
语法
watch: {
o1: () => {} // 别用箭头函数,这里的this是全局对象
o2: function(value, oldValue){},
o3(){},
o4: [f1, f2],
o5: 'methodName',
o6: {handler:fn, deep:true, immediate:true},
'object.a': function(){}
}
使用
当数据变化时,执行一个函数,比如:
//实现数据的变化及撤销
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
new Vue({
data :{
n:0,
history:[],
inUndoMode:false
},
watch:{
n:function(newVulue,oldVulue){
if(!this.inUndoMode){
this.history.push({from:oldVulue,to:newVulue})
}
},
},
methods:{
add1(){
this.n+=1
},
add2(){
this.n+=2
},
minus1(){
this.n-=1
},
minus2(){
this.n-=2
},
undo(){
const last =this.history.pop()
this.inUndoMode=true
const old =last.from
this.n=old//watch 里面的n是异步的
this.$nextTick(() => {
this.inUndoMode = false
})
}
},
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>
`
}).$mount("#app");
运行动画
使用 watch 的深度遍历(deep)和立即调用(inmediate)功能
deep 设置为 true 用于监听对象内部值的变化
immediate 设置为 true 将立即以表达式的当前值触发回调
<template>
<button @click="obj.a = 2">修改</button>
</template>
<script>
export default {
data() {
return {
obj: {
a: 1,
}
}
},
watch: {
obj: {
handler: function(newVal, oldVal) {
console.log(newVal);
},
deep: true,
immediate: true
}
}
}
</script>
以上代码我们修改了 obj 对象中 a 属性的值,我们可以触发其 watch 中的 handler 回调输出新的对象,而如果不加 deep: true,我们只能监听 obj 的改变,并不会触发回调。同时我们也添加了 immediate: true 配置,其会立即以 obj 的当前值触发回调。
两者之间对比
从上面流程图中,我们可以看出它们之间的区别:
- computed:监测的是依赖值,依赖值不变的情况下其会直接读取缓存进行复用,变化的情况下才会重新计算。
- watch:监测的是属性值, 只要属性值发生变化,其都会触发执行回调函数来执行一系列操作。
除此之外,有点很重要的区别是:计算属性不能执行异步任务,计算属性必须同步执行。也就是说计算属性不能向服务器请求或者执行异步任务。如果遇到异步任务,就交给侦听属性。watch也可以检测computed属性。