学习vue3第六节(vue3 中 computed watch watchEffect)
1、computed
计算属性:computed 原理:不管是 Vue2 中还是在 Vue 3 中,计算属性会基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。 在对赋值求值或者大数据求值会起到一定性能优化的作用。
在 Vue 中计算属性会计算出一个新的属性,并将这个属性挂载到 vm(Vue 实例上)。而相对的还有侦听器 watch,侦听器是监听 vm 上已经存在的响应式属性,所以可以用侦听器监听计算属性。
计算属性本质是一个惰性求值的观察者,具有缓存性,只有当依赖发生改成时,才会重新求值。侦听器是当依赖数据发生改变时就会执行回调。
在使用场景上,计算属性适合在一个数据被多少数据影响时使用,而侦听器适合在一个数据影响多个数据。接下来我们就通过源码来看看 computed 的实现原理。
而在vue3 中,computed 有两种形式:
1、直接接收一个getter函数,并且getter()函数会返回一个不可改变的响应式ref对象,注意是不可改变的,
2、接受一个对象,对象中包含 get()、set() 方法,通过get()、set()进行读写ref对象
computed / watch / watchEffect 含义
监听数据的异同
监听一个属性,多个属性,ref对象,reactive对象
<template>
<div class="watch-container">
年龄:{{ age }}
<br>
姓名:{{ name }}
<br>
职业:{{ person.job }}
<br>
爱好:{{ person.love }}
</div>
<div>
otherPerson==={{ JSON.stringify(otherPerson) }}
年龄:{{ otherPerson.age.value }}
</div>
<button @click="handleChangeAge()">change age</button>
<div>==============</div>
<div>personAllName:{{ personAllName }}</div>
<div>lastName: {{ lastName }}</div>
<br>
<button type="primary" @click="handleChangeLastName">change person laset name</button>
</template>
<script setup>
import { ref, reactive, computed} from 'vue'
const age = ref(0)
const name = ref('Andy')
const person = reactive({
job: '搬砖',
love: '音乐'
})
// 1、传入getter 函数
const otherPerson = computed(() => {
return {
age,
name,
...person
}
},
{
onTrack(e) {
// 当 otherPerson.age 被追踪为依赖时触发
debugger
},
onTrigger(e) {
// 当 otherPerson.age 被更改时触发
debugger
}
}
)
const handleChangeAge = () => {
age.value++
console.log('====')
}
// 2、 传入对象,可以进行读写操作
let firstName = ref('zhang')
let lastName = ref('Andy')
const personAllName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(otherPersonName) {
lastName.value = otherPersonName
}
})
const handleChangeLastName = () => {
personAllName.value = '下一个肖申克' + "$"
}
</script>
实现方式上,vue3与vue2中的computed() 都是基于响应式依赖进行缓存的,
但是 vue3 中的computed需要依赖于 ref() 和 reactive() 响应属性的值来实现,而effect则是依赖于computed() 来实现的
当我们在调用 computed 方法时,就会在这里需要统一做下区分,同时调用实现类 ComputedRefImpl,这个方法比较简单,接下来我们重点分析下类 ComputedRefImpl。
<script>
function computed(getterOrOptions, ...) {
let getter;
let setter;
const onlyGetter = isFunction(getterOrOptions);
if (onlyGetter) {
getter = getterOrOptions;
setter = () => {
console.warn('Write operation failed: computed value is readonly');
}
;
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR);
...
return cRef;
}
// ComputedRefImpl 类
class ComputedRefImpl {
constructor(getter, _setter, isReadonly, isSSR) {
this._setter = _setter;
this.dep = undefined;
this.__v_isRef = true;
this._dirty = true;
// 创建effect对象,生成watcher监听函数,并将值赋给实例的 effect属性,
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true;
triggerRefValue(this);
}
});
this.effect.computed = this;
this.effect.active = this._cacheable = !isSSR;
// 根据传入是否有setter函数来决定是否只读;设置ref是否为只读对象,
this["__v_isReadonly" /* IS_READONLY */] = isReadonly;
}
get value() {
const self = toRaw(this);
trackRefValue(self);
if (self._dirty || !self._cacheable) {
self._dirty = false;
self._value = self.effect.run();
}
return self._value;
}
set value(newValue) {
this._setter(newValue);
}
}
</script>
若需要computed 实际案例,请留言说明具体业务需求,在线提供代码
2、watch
侦听器,只要监听的响应式属性|对象|数组发生变化就会触发回调函数:
watch(
source,
callback,
{
deep:true,
immediate:true,
flush:'pre'|'post'|'async',
onTrack(){
// 调试
},
onTrigger(){
debugger
},
once: true // 是否只执行一次
}
)
侦听的数据源 source :
1、响应式对象reactive;
2、一个ref属性(包括computed),
3、一个getter()函数;
4、多个数据源组成的数组;
<script setup>
import { ref, reactive, watch } from 'vue'
const firstName = ref('张')
const lastName = ref('Andy')
const person = reactive({
name: 'Andy',
job: '专业搬砖'
})
</script>
// 监听单个ref
watch(firstName, (newValue, oldValue) => {
console.log('firstName===',newValue, oldValue)
})
// 监听reactive对象
watch(person, (newValue, oldValue) => {
// 此时newValue === oldValue // true 是相当的
// 因为他们是同一个对象
})
// 若要监听reactive对象中的某个属性,需要如下:
// 若要监听 person中job改变,使用getter函数
watch(
() => person.job, // 第一个参数需要是 函数,
(newValue, oldValue) => {
// 此时可以监听到 newValue 与 oldValue 不相等
},
{
deep: true, // 默认false, 是否启用深度监听,与vue2 语法一样,
immediate: true // 默认false, 是否立即执行,在页面初始化时候需要执行回调函数时,可以添加此属性,第一次调用时旧值是 undefined,
flush: 'pre'|'post' | 'async', //默认是pre, 如果想在侦听器回调中能访问被 Vue 更新之后的所属组件的 DOM,可以使用 post,如果想同步执行可以使用 async;(分别有对应的语法糖,watchPostEffect(),watchAsyncEffect(), 下节说明)
onTrack(){},
onTrigger(){},
once: false, // 默认false 是否只执行一下 v3.4 之后才有
}
)
// 监听多数据源
watch(
[lastName, ()=> person.name],
([lastNameNew, personNameNew], [lastNameOld, personNameOld]) => {
// 此时回调函数中需要是对应的新旧值数组,顺序与 source 中数组属性顺序一致;
})
// 停止侦听器
const stopWatch = watch(firstName, () => {
//
})
// 执行 stopWatch() 即可停止侦听器;
// 清除副作用,比如深度监听、立即执行时候,里面有请求后台的接口,我们希望的是使用最新的数据参数,进行处理逻辑??? 超出三界之外,后续补充!!!
3、watchEffect
// watchEffect 立即运行一个函数,同时响应式的追踪其依赖,并且能够在依赖发生变化时候,重新执行函数,可以监听页面中所有ref、reactive声明的属性、对象、等响应式变量;
// 动态新加的属性 同样会监听到,收集所有依赖监听执行;
//
let stopWatchEffect = watchEffect(() => {
// 第一个参数:回调函数
}, {
// 第二参数:
flush: 'pre', 'post', 'sync', // 默认是pre,参考watch对应用法
onTrack(e) { // 调试
debugger
},
onTrigger(e) {
debugger
}
},
)
// watch 需要显性的指定需要监听的对象,属性,可以自己配置对应的属性,如是否立即执行(immediate: true),是否需要深度监听(deep: true)
// watchEffect 则会自动收集需要响应式对象属性,并且会在对象或属性发生变化时候,立即执行副作用函数,而不需要手动配置;
欢迎各位大神在线批评指导!!!