a) computed
computed函数用来创造计算属性,和过去一样,它返回的值是一个ref对象。里面可以传方法,或者一个对象,对象中包含set()、get()方法
只读的计算属性:
姓: <input type="text" v-model="person.firstName" /> <br />
名: <input type="text" v-model="person.lastName" /> <br />
<span>全名:{{ person.fullName }}</span>
import { reactive, computed } from "vue";
setup() {
const person = reactive({
firstName: "张",
lastName: "三",
});
person.fullName = computed(() => {
return person.firstName + "-" + person.lastName;
});
return { person };
},
可读可改的计算属性:
姓: <input type="text" v-model="person.firstName" /> <br />
名: <input type="text" v-model="person.lastName" /> <br />
全名:<input type="text" v-model="person.fullName" /> <br />
import { reactive, computed } from "vue";
setup() {
const person = reactive({
firstName: "张",
lastName: "三",
});
//完整形式
person.fullName = computed({
get() {
return person.firstName + "-" + person.lastName;
},
set(value) {
const nameArr = value.split("-");
person.firstName = nameArr[0];
person.lastName = nameArr[1];
},
});
return { person };
},
b) watch
与vue2中watch配置功能一致
- 监视reactive定义的响应式数据时:oldValue无法正确获取,强制开启了深度监视(deep配置失效)
- 监视reactive定义的响应式数据中某个属性(对象)时,deep配置有效
1.监视ref定义的一个响应式对象
let count = ref(0);
let msg = ref("你好啊");
const person = reactive({
name: "张三",
age: 18,
jobs: {
job1: {
salary: 20,
},
},
});
watch(count, (newVal, oldVal) => {
console.log("count的值改变了", newVal, oldVal);
});
2.监视ref定义的多个响应式对象
watch(
[count, msg],
(newVal, oldVal) => {
console.log("count或者msg的数据改变了", newVal, oldVal);
},
{ immediate: true, deep: true }
);
3.监视reactive定义的响应式对象的全部属性,但是这里无法正确的获取oldVal,强制开启了深度监视(deep配置无效)
watch(person, (newVal, oldVal) => {
console.log("person改变了", newVal, oldVal);
},{deep:false});//deep配置无效
4.监视reactive定义的响应式数据的一个属性,这种方式可获取oldVal
watch(
() => person.age,
(newVal, oldVal) => {
console.log("person的age改变了", newVal, oldVal);
}
);
5.监视reactive定义的响应式数据的某些属性,这种方式可获取oldVal
watch([() => person.age, () => person.name], (newVal, oldVal) => {
console.log("person的age或name改变了", newVal, oldVal);
});
6.特殊情况,监视reactive定义的响应式对象中的某个对象,这里deep配置有效,获取不到oldVal
watch(
() => person.jobs,
(newVal, oldVal) => {
console.log("person的job改变了", newVal, oldVal);
},
{ deep: true }
);
7.ref定义的响应式对象
如果定义的对象是基本数据,则不用加.value,例如 let count=ref(0),如果在监视时加了.value,则监视的则是0这个数字,不是我们想要的refImpl数据对象,如果定义的对象是一个对象,则需要加.value,否则监听时虽然对象内的数据改变,但整个对象没有改变(其内存地址没有发生变化),会认为没有发生变化,也就监听不到对象内数据的变化
const person = ref({
name: "张三",
age: 18,
jobs: {
job1: {
salary: 20,
},
},
});
1.数据为对象时,需要加.value
watch(person.value, (newVal, oldVal) => {
console.log("person的值改变了", newVal, oldVal);
});
2.若不加.value,则需要深度监听(deep:true),才能监听到对象的改变
watch(
person,
(newVal, oldVal) => {
console.log("person的值改变了", newVal, oldVal);
},
{ deep: true }
);
watch和watchEffect
a) 纯函数与副作用
effect全称叫side effect,副作用。
什么是副作用呢,一个函数运行后产生了可以影响其外部或可以看到的效果,就叫副作用,比如document. Body. Append,alert再或者是showModel(在页面中展示一个弹层),或者window.open打开一个新窗口。
// 纯函数:如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。它不应修改程序的状态或引起副作用。
function priceAfterTax(productPrice) {
return (productPrice * 0.20) + productPrice;
}
// 副作用:一个可以被观察的副作用是在函数内部与其外部的任意交互。这可能是在函数内修改外部的变量,或者在函数里调用另外一个函数等。
var tax = 20;
function calculateTax(productPrice) {
tax = tax/100
return (productPrice * tax) + productPrice;
}
react中副作用可以这样理解:组件初始化的时候,组件根据开发者的设定,由自身驱动的第一次DOM修改,就是主作用。主作用之后,组件开始执行用户逻辑,这时你眼里的业务逻辑代码,在React眼里都是副作用。
vue3中的副作用与react中副作用的定义类似,响应式数据的变更造成的其他连锁反应,以及后续逻辑,这些连锁反应都叫副作用。
哪些函数没有副作用呢,只用来计算结果的函数,比如Math.max,JSON.parse,它们的运行除了返回结果外不会有其它效果,这就叫不产生副作用。
基本上可以简化的理解为副作用就是执行某种操作(副作用函数),无副作用就是执行某种计算(纯函数)。
这里watchEffect的意思就是在观察(watch)到变化后执行一些操作(effect)。
b) watchEffect的基本使用
import { watchEffect, ref } from 'vue'
setup () {
const userID = ref(0)
watchEffect(() => console.log(userID))
setTimeout(() => {
userID.value = 1
}, 1000)
return {
userID
}
}
c) watch和watchEffect的异同
watch和watchEffect都可以侦听副作用,区别在于:
- 第一点我们可以从示例代码中看到
watchEffect不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行;而watch只能监听指定的属性而做出变更。 - 第二点就是 watch 可以获取到新值与旧值(更新前的值),而
watchEffect是拿不到的。 - 第三点是 watchEffect 如果存在的话,在组件初始化的时候就会执行一次用以收集依赖(与
computed同理),而后收集到的依赖发生变化,这个回调才会再次执行;而 watch 不需要,因为他一开始就指定了依赖。
d) watchEffect的刷新时机
watchEffect会在所有的组件 update 前执行。
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
setup() {
const count = ref(0)
watchEffect(() => {
console.log(count.value)
})
return {
count
}
}
}
</script>
在这个例子中:
count会在初始运行时同步打印出来,因为watchEffect会在组件初始化的时候默认收集一次依赖- 更改
count时,将在组件更新前执行副作用。
如果需要在组件更新(例如:当与模板引用一起)后重新运行侦听器副作用,我们可以传递带有 flush 选项的附加 options 对象 (默认为 'pre'):
watchEffect(
() => {
/* ... */
},
{
flush: 'post'
}
)
e) 停止侦听
当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
在一些情况下,也可以显式调用返回值以停止侦听:
const stop = watchEffect(() => {
/* ... */
})
stop()
f) 清除副作用的函数
watchEffect函数的onInvalidate方法就是用来清除副作用的,但副作用不一定是不被需要的。它可以是获取数据、事件监听或订阅、改变应用状态、修改 DOM、输出日志等等。清除副作用实际上是Vue3提供给用户的一种取消异步副作用的实现方法。
<template>
<div>
<div>{{count}}</div>
<button @click="doAdd">点我+1</button>
</div>
</template>
<script>
import { ref, watchEffect, watch } from "vue";
export default {
setup() {
const count = ref(1);
const doAdd = ()=>{
count.value++
}
watchEffect(onInvalidate => {
console.log(count.value)
// 异步api调用,返回一个操作对象
const timer = setInterval(() => {
console.log("timer执行了")
}, 1000);
//onInvalidate(fn)传入的回调会在watchEffect重新运行或者watchEffect停止的时候执行。
onInvalidate(()=>{
clearInterval(timer)
})
});
return {
count,
doAdd
};
}
};
</script>
