【Vue3】computed、watch、watchEffect
先比较一下
三者都是侦听工具,实现的是观察者模式,所以我们可以把三者放在一起,做一下横向对比,辅助大家理解。
对比依据 | computed | watch | watchEffect |
---|---|---|---|
是否自动收集依赖(1) | 自动 | 需要指定依赖对象 | 自动 |
有无返回值 | 有 | 无 | 有 |
是否可以赋值 | 可以 | 不能 | 不能 |
使用场景 | 简单情况 | 复杂情况 | 简单情况 |
是否立即执行 | 是 | 看参数(2) | 是 |
本质 | class | function | function |
- (1)依赖:指的是响应性依赖,也就是侦听 ref、reactive 这类具有响应性的对象。
- (2)watch:默认情况下,被侦听对象变化时才会执行,但是可以通过 options 参数设置为可以立即执行
computed
computed 这个单词比较有误导性,看表面是计算用的,但是除了计算之外还可以有其他的功能,比如做个判断、做个序列化、赋值等。
但这些都不是重点,重点是,它和 watch 一样,是侦听用的。
自动侦听 getter 里有响应性的对象,当发生变化时重新执行一次 get 函数,将返回结果存入 _value 缓存,并且通知 template 刷新页面。
所以说,计不计算的不是重点,侦听才是重点。
基础示例
举个例子,我想在网页里看看对象的序列化,用 computed 的实现方式:
const model = reactive({
name: '演示一下'
})
const json = computed(() => {
return JSON.stringify(model, null, 2)
})
定义一个 reactive 和 一个 computed,使用 JSON.stringify 实现序列化。
<el-input
v-model="json"
:rows="5"
type="textarea"
>
</el-input>
自动收集依赖,侦听 model 的变化,序列化之后交给 template 的 el-input。
这样我们就可以在网页里面实时看到对象的序列化情况了。
结构和本质
我们打印一下 computed ,看看结构:
看起来挺复杂的,其实简化一下就简单了,主体是一个 class,其中有若干“私有成员”,然后用访问器实现我们常用的 value 属性,即:
class myComputed {
#_value: any
constructor(getter, _setter) {
// 设置内部成员
// 相关操作
}
get value() {
// 相关操作,比如查看缓存是否需要更新
return this.#_value
}
set value(newValue){
// 相关操作
this._setter(newValue)
}
}
主要结构是这样,当然还需要很多细节才能实现 computed 的功能,如果对细节感兴趣的话,可以看看 Vue 的源码。
计算属性缓存
其实看看源码就好理解了,执行 get 方法时,会把返回值存入 _value ,template 直接从 _value 属性获取数据,在需要更新缓存的时候才会调用 getter,这样就实现了缓存功能。
也就是说,使用 _value 属性缓存了执行结果。
可以赋值的方式
computed 也可以开启赋值模式,方法就是传入一个对象,拥有 get 和 set 的对象,可以用在 props 在模板上的绑定。
props 的属性是只读的,不能用于 template 的 v-model,那么怎么办?我们可以使用 computed 的赋值模式作为中转站:
- 子组件:
const props = defineProps<{
modelValue: string
}>()
const emit = defineEmits(['update:modelValue'])
const myName = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
在子组件里面定义 props 和 emit,然后可以用computed实现“中转”功能,get 里面获取 props 的属性值,set 里面使用 emit 提交更改申请。
当然,现在 Vue3.3+ 推出了语法糖 defineModel,就不需要我们自己折腾了。
watch
composition API 的 watch 非常好理解,他是一个函数,第一个参数要指定一个被侦听对象,第二个参数是回调函数,在被侦听的对象发生变化时,执行回调函数。(当初看option 的watch,怎么看都看不懂。。。)
基础示例
还是上面那个例子,我们看看用 watch 如何实现:(watch 并不适合实现这样的需求,这里仅做对比)
const josn = ref('')
watch(model, () => {
josn.value = JSON.stringify(model, null, 2)
})
我们先定义一个 ref,然后用 watch 侦听 model,发生变化的时候再做序列化的操作,给 ref 赋值。
对比一下就会发现,对于这种简单的需求,使用 watch 明显没有使用 computed 方便。
不过 watch 可以明确指定侦听的对象,这样对于复杂的情况就不容易乱,不用担心自动收集依赖,把不想被侦听的对象也给收集进来。
watch的更多用法可以看看官网
watchEffect
一开始没弄明白 watchEffect,后来把三者放在一起一对比就好懂了。
- 和 computed 对比,相当于没有返回值的 computed,当然也不能 set。
- 和 watch 对比,可以自动收集依赖,无需手动指定。
这样想就清晰了,排列组合的结果。
是否自动收集依赖、是否有返回值组合一下,
- 自动收集依赖、无返回:watchEffect
- 自动收集依赖、有返回:computed
- 需要指定依赖、无返回:watch
那么需要指定依赖,还有返回的是谁呢?好像没有。。。
基础示例
还是上面那个例子:
const josn = ref('')
watchEffect(() => {
josn.value = JSON.stringify(model, null, 2)
})
不需要自定侦听对象,而是像computed那样可以自动收集,然后还是当model变化时执行序列化的操作,赋值给 ref。
官网的例子只有一个 console.log ,还以为只是做调试用的呢。。。
当然 watchEffect 还有其的功能,比如停止侦听等,这里就不细说了,可以看看官网。