Skip to content
本页目录

【Vue3】computed、watch、watchEffect

先比较一下

三者都是侦听工具,实现的是观察者模式,所以我们可以把三者放在一起,做一下横向对比,辅助大家理解。

对比依据computedwatchwatchEffect
是否自动收集依赖(1)自动需要指定依赖对象自动
有无返回值
是否可以赋值可以不能不能
使用场景简单情况复杂情况简单情况
是否立即执行看参数(2)
本质classfunctionfunction
  • (1)依赖:指的是响应性依赖,也就是侦听 ref、reactive 这类具有响应性的对象。
  • (2)watch:默认情况下,被侦听对象变化时才会执行,但是可以通过 options 参数设置为可以立即执行

computed

computed 这个单词比较有误导性,看表面是计算用的,但是除了计算之外还可以有其他的功能,比如做个判断、做个序列化、赋值等。

但这些都不是重点,重点是,它和 watch 一样,是侦听用的。

自动侦听 getter 里有响应性的对象,当发生变化时重新执行一次 get 函数,将返回结果存入 _value 缓存,并且通知 template 刷新页面。

所以说,计不计算的不是重点,侦听才是重点。

基础示例

举个例子,我想在网页里看看对象的序列化,用 computed 的实现方式:

javascript
  const model = reactive({
    name: '演示一下'
  })
  const json = computed(() => {
    return JSON.stringify(model, null, 2)
  })

定义一个 reactive 和 一个 computed,使用 JSON.stringify 实现序列化。

html
  <el-input
    v-model="json"
    :rows="5"
    type="textarea"
  >
  </el-input>

自动收集依赖,侦听 model 的变化,序列化之后交给 template 的 el-input。

这样我们就可以在网页里面实时看到对象的序列化情况了。

结构和本质

我们打印一下 computed ,看看结构:

550computed的结构.jpg

看起来挺复杂的,其实简化一下就简单了,主体是一个 class,其中有若干“私有成员”,然后用访问器实现我们常用的 value 属性,即:

javascript
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 还有其的功能,比如停止侦听等,这里就不细说了,可以看看官网