1️⃣ 计算属性
2️⃣ 基础例子
<template>
<div id="app">
<div>{{ count }}</div>
<div @click="num1++">Add</div>
</div>
</template>
<script>
export default {
name: "App",
data: function() {
return {
num1: 10,
num2: 20,
};
},
computed: {
// 计算属性的简写形式
count() {
return this.num1 + this.num2;
},
},
};
</script>
2️⃣ 计算属性缓存 vs 方法
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
当使用方法时,每一次页面重新渲染,对应的方法都会重新执行一次,但是利用计算属性做,就不会有这样的现象出现。计算属性有缓存而方法没有缓存。
我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。
2️⃣ 计算属性 vs 侦听属性
两者都可以观察和响应 Vue 实例上的数据的变动。
侦听器(watch)擅长处理的场景是:一个数据影响多个数据。计算属性(computed)擅长处理的场景是:多个数据影响一个数据。
在侦听器中可以执行异步,但是在计算属性中不可以。
2️⃣ 计算属性的 setter
计算属性除了写成一个函数之外,还可以写成一个对象,对象内有两个属性,getter & setter,这两个属性皆为函数,写法如下:
const vm = new Vue({
el: "#app",
computed: {
setAget: {
getter() {
// 一些代码
},
setter() {
// 一些代码
},
},
},
});
3️⃣ getter 读取
在前面,我们直接将计算属性写成了一个函数,这个函数即为 getter 函数。也就是说,计算属性默认只有 getter。
1. getter 的 this,被自动绑定为 Vue 实例。
当我们去获取某一个计算属性时,就会执行 get 函数。
get 被调用的时间
1. 当计算属性被初次调用时
2. 当计算属性依赖的属性被修改时
<template>
<div id="app">
<!-- 计算属性有缓存机制 多次调用 get 只会被调用一次 -->
<div>{{ count }}</div>
<div>{{ count }}</div>
<div>{{ count }}</div>
<div @click="num1++">Add</div>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
num1: 10,
num2: 20,
};
},
computed: {
count: {
get() {
console.log("get 被调用了");
let num = this.num1 + this.num2;
return num;
},
},
},
};
</script>
3️⃣ setter 设置
set 函数在给计算属性重新赋值时会执行。set 的参数为被重新设置的值。
1. setter 的 this,被自动绑定为 Vue 实例。
set 的调用时间
1. 当计算属性本身被修改时 set 被调用,一般情况下计算属性是不修改的 set 的。
要注意,即使给计算属性赋了值,计算属性也不会改变,只有当依赖的响应式属性变化了,计算属性才会重新计算。
<template>
<div id="app">
<div>{{ count }}</div>
<div @click="count = 50">Add</div>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
num1: 10,
num2: 20,
};
},
computed: {
count: {
get() {
console.log("get 被调用了");
let num = this.num1 + this.num2;
return num;
},
set(newV) {
console.log("set 被调用了");
this.num1 = newV;
},
},
},
};
</script>
1️⃣ 侦听器
侦听属性,响应数据( data&computed )的变化,当数据变化时,会立刻执行对应函数
1. 侦听器函数,会接收两个参数,第一个参数为 newVal ( 被改变的数据 ),第二个参数为 oldVal ( 赋值新值之前的值 )。
<template>
<div id="app">
<div>{{ num }}</div>
<h3 @click="change">change</h3>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
num: 100,
};
},
watch: {
num(newVal, newOld) {
console.log(newVal, newOld, "触发 watch");
},
},
methods: {
change() {
if (this.num === 100) {
this.num = 200;
} else {
this.num = 100;
}
},
},
};
</script>
2️⃣ 字符串类型
值为方法名字,被侦听的数据改变时,会执行该方法。
<template>
<div id="app">
<div>{{ num }}</div>
<h3 @click="change">change</h3>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
num: 100,
};
},
watch: {
num: "watchMethod",
},
methods: {
change() {
if (this.num === 100) {
this.num = 200;
} else {
this.num = 100;
}
},
watchMethod(newVal, oldVal) {
console.log(newVal, oldVal, "触发 watch 调用指定方法");
},
},
};
</script>
2️⃣ 对象类型
写成对象类型时,可以提供选项。
1. handler:handler 是被侦听的数据改变时执行的回调函数。handler 的值类型为函数/字符串,写成字符串时为一个方法的名字。
2. deep:在默认情况下,侦听器侦听对象只侦听引用的变化,只有在给对象赋值时它才能被监听到。所以需要使用 deep 选项,让其可以发现对象内部值的变化,将 deep 的值设置为 true,那么无论该对象被嵌套的有多深,都会被侦听到。
3. immediate**:**加上 immediate 选项后,回调将会在侦听开始之后立刻被调用。而不是等待侦听的数据更改后才会调用。
<template>
<div id="app">
<div>{{ num.num1 }}</div>
<h3 @click="change">change</h3>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
num: {
num1: 100,
},
};
},
watch: {
num: {
handler(newVal, newOld) {
console.log(newVal, newOld, "触发 watch");
},
deep: true,
immediate: true,
},
},
methods: {
change() {
if (this.num.num1 === 100) {
this.num.num1 = 200;
} else {
this.num.num1 = 100;
}
},
},
};
</script>
2️⃣ 数组类型
可以将多种不同值类型写在一个数组中。
<template>
<div id="app">
<div>{{ num }}</div>
<h3 @click="change">change</h3>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
num: 100,
};
},
watch: {
num: [
"watchMethod",
function() {
console.log("触发 watch function 方法被调用了");
},
{
handler(newVal, newOld) {
console.log(newVal, newOld, "触发 watch");
},
deep: true,
immediate: true,
},
],
},
methods: {
change() {
if (this.num === 100) {
this.num = 200;
} else {
this.num = 100;
}
},
watchMethod(newVal, newOld) {
console.log(newVal, newOld, "触发 watch 调用指定方法");
},
},
};
</script>
2️⃣ 键类型
当 key 值类型为字符串时,可以实现监听对象当中的某一个属性,如:
<template>
<div id="app">
<div>{{ num.num1 }}</div>
<h3 @click="change">change</h3>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
num: {
num1: 100,
},
};
},
watch: {
"num.num1"(newVal, newOld) {
console.log(newVal, newOld, "触发 watch");
},
},
methods: {
change() {
if (this.num.num1 === 100) {
this.num.num1 = 200;
} else {
this.num.num1 = 100;
}
},
},
};
</script>
1️⃣ vm.$watch
Vue 实例将会在实例化时调用 $watch,遍历 watch 对象的每一个属性。我们也可以利用 vm.$watch 来实现侦听,用法与 watch 选项部分一致,略有不同。以下为使用方法。
2️⃣ 基本用法
<div>
<p>FullName: {{fullName}}</p>
<p>FirstName: <input type="text" v-model="firstName"></p>
</div>
new Vue({
el: '#root',
data: {
firstName: 'Dawei',
lastName: 'Lou',
fullName: ''
},
watch: {
firstName(newName, oldName) {
this.fullName = newName + ' ' + this.lastName;
}
}
})
2️⃣ handler 方法和 immediate 属性:
上面的例子是值变化时候,watch才执行,我们想让值最初时候watch就执行就用到了**handler**
和**immediate**
属性
watch: {
firstName: {
handler(newName, oldName) {
this.fullName = newName + ' ' + this.lastName;
},
// 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法,如果设置了false,那么效果和上边例子一样
immediate: true
}
}
2️⃣ deep 属性( 深度监听,常用于对象下面属性的改变 ):
<div>
<p>obj.a: {{obj.a}}</p>
<p>obj.a: <input type="text" v-model="obj.a"></p>
</div>
new Vue({
el: '#root',
data: {
obj: {
a: 123
}
},
watch: {
obj: {
handler(newName, oldName) {
console.log('obj.a changed');
},
deep: true
}
}
})
2️⃣ 侦听某个数据的变化
// 1. 三个参数,一参为被侦听的数据;二参为数据改变时执行的回调函数;三参可选,为设置的选项对象
vm.$watch(
"msg",
function () {
},
{
deep: true,
immediate: true,
}
);
// 2. 二个参数,一参为被侦听的数据;二参为选项对象,其中 handler 属性为必需,是数据改变时执行的回调函数,其他属性可选。
vm.$watch(
"msg",
{
handler() {
},
deep: true,
immediate: true,
}
);
2️⃣ 侦听某个对象属性的变化
vm.$watch(
"msg.data",
{
handler() {
},
deep: true,
immediate: true,
}
);
2️⃣ watch 多个属性
<script>
export default {
data() {
return {
name: '',
age: '',
hobby: {
aaa: '',
bbb: '', // 不观察 bbb
ccc: ''
}
}
},
computed: {
watchMore() {
const { name, age } = this
const { aaa, ccc } = this.hobby
return {
name,
age,
aaa,
ccc
}
}
},
watch: {
watchMore(val) {
console.log(val)
}
}
}
</script>
2️⃣ 当监听的数据的在初始不确定,由多个数据得到时,此时可以将第一个参数写成函数类型
<!-- ********** HTML ********** -->
<body>
<div0 id="app">
<h1>{{msg1}}</h1>
<h1>{{msg2}}</h1>
<button @click="add" type="button">Add</button>
</div0>
</body>
// ********** JS/VUE **********
const vm = new Vue({
el: '#app',
data: {
msg1: 10,
msg2: 20
},
methods: {
add() {
this.msg1++
this.msg2++
},
}
})
vm.$watch(
function () {
// 表达式`this.msg1 + this.msg2`每次得出一个不同的结果时该函数都会被调用
// 这就像监听一个未被定义的计算属性
return this.msg1 + this.msg2;
},
{
handler(newVal, oldVal) {
// 每次 this.msg1 + this.msg2 得出一个不同的值时就会调用该函数
console.log(newVal, oldVal);
},
deep: true,
immediate: true,
}
);
2️⃣ 取消侦听函数
侦听器函数执行后,会返回一个取消侦听函数,用来停止触发回调:
<!-- HTML -->
<body>
<div id="app">
<h1>{{msg1}}</h1>
<h1>{{msg2}}</h1>
<button @click="add" type="button">Add</button>
<button @click="cancel" type="button">Cancel</button>
</div>
</body>
// JS/VUE
const vm = new Vue({
el: '#app',
data: {
msg1: 10,
msg2: 20
},
methods: {
add() {
this.msg1++
this.msg2++
},
cancel() {
unwatch()
}
}
})
const unwatch = vm.$watch(
function () {
return this.msg1 + this.msg2;
},
{
handler(newVal, oldVal) {
console.log(newVal, oldVal);
},
deep: true,
immediate: true,
}
);
使用 unwatch 时,需要注意的是,在带有 immediate 选项时,不能在第一次回调时取消侦听数据。
// JS/VUE
const unwatch = vm.$watch(
'msg',
function () {
unwatch(); // 此时会报错
},
{
immediate: true
}
})
如果仍然希望在回调内部用一个取消侦听的函数,那么可以先检查该函数的可用性:
// JS/VUE
const vm = new Vue({
el: '#app',
data: {
msg: 100,
},
methods: {
add() {
this.msg++
},
}
})
var unwatch = vm.$watch(
'msg',
function () {
if (unwatch) {
unwatch();
}
},
{
immediate: true
}
);