对比 watch vs watchEffect vs computed

849 阅读7分钟

来说说自己的理解

  • 这几个 api 真挺难分清楚的 刚开始学的时候迷迷糊糊 区别以及应用场景 都感觉答不上来

  • 所以也挺多时间进行一个梳理和区分 源码看了一些 就目前自己的了解做一个记录吧

  • 我觉得要理解的话 可以从这几个点出发

    • 为什么会设计出这个 属性?他是基于什么原因设计的?用于解决什么问题?

    • 这几个 api 实现思路是怎样的?(读源码了)

    • 他们之间的具体区别?

      • 知道区别就能分析出应用场景了 什么时候该使用哪个
    • 使用的时候一些坑 一些细节?

computed

  • 例子

    • <div id="computed-basics">
        <p>Has published books:</p>
        <span>{{ publishedBooksMessage }}</span>
      </div>
      ​
      ​
      data() {
          return {
            author: {
              name: 'John Doe',
              books: [
                'Vue 2 - Advanced Guide',
                'Vue 3 - Basic Guide',
                'Vue 4 - The Mystery'
              ]
            }
          }
        },
      computed: {
          // 计算属性的 getter
          publishedBooksMessage() {
            // `this` 指向 vm 实例
            return this.author.books.length > 0 ? 'Yes' : 'No'
          }
        }
      
  • 首先计算属性是基于方法来升级的 上面的数据变动 完全可以使用一个函数来实现

    • 但是我们希望得到的效果是 当数据发生变化时 与其相关联的数据自动发生改变

    • 也就是说 自动执行相关联的函数 其实响应式的具体实现思想也跟这个类似

      • 大概就是 先收集每一个响应式变量所依赖的函数(这些函数也被称为 副作用函数) 当这个变量发生改变是 与这个变量相关联的函数会全部重新执行一次 那么其他使用到这个变量的数据也会响应改变了
  • 另一个就是 简化模板中的复杂表达式

    • {{ firstName + lastName }}
      ​
      {{ fullName }}
      ​
      computed: {
        fullName: {
          get() {
            return this.firstName + this.lastName
          }
        }
      }
      
  • 官网提到 计算属性将基于变量的响应依赖关系缓存 计算属性只会在相关响应式依赖发生改变时重新求值 这就意味着只要 author.books 还没有发生改变 多次访问 publishedBookMessage 时计算属性会立即返回之前的计算结果 而不必再次执行函数

    • 相比之下,每当触发重新渲染时,调用方法将始终会再次执行函数

      • 渲染和刷新还是有区别的 渲染是数据更新 刷新是整个页面
  • 那么怎么理解他的缓存性呢

    • 这里我觉得还是要读一下源码比较好理解 大概说一下为什么具有缓存行吧 我刚开始对这一块的理解听迷糊的

    • 首先 我们知道 当他发现数据变化时 就会重新计算并且返回 而缓存性就在于 当我们一二次第三次访问 这个属性的时候 它不需要重新计算

      • 比如 上面 this.author.books 数据改变了 那么会重新计算 但是有很多位置引入了这个 this.author.books 第一个位置读取的时候 计算返回了 那么第一个位置 或者第二次读取的时候 就不会再重新计算了 而是直接返回之前的计算结果 源码主要是通过 dirty: Boolean 来判断是否要重新计算
  • 写法

    • const count = ref(1)
      const plusOne = computed(() => count.value + 1)
      ​
      const count = ref(1)
      const plusOne = computed({
        get: () => count.value + 1,
        set: val => {
          count.value = val - 1
        }
      })
      

watch

  • computed 只是为了获取一个返回值 但是有时数据改变时 我们需要执行一些操作 那么就有了 watch

  • 官网中提到 Vue 通过 watch 选项提供了一个更通用的方法来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的

  • 例子的话看官网的就行了

    • 大概就是数据发生变化 就发送一个 ajax 请求重新获取数据
  • 写法

    • // 侦听一个 getter
      const state = reactive({ count: 0 })
      watch(
        // 这个箭头函数 就是简化的 get 
        () => state.count,
        (count, prevCount) => {
          /* ... */
        }
      )
      ​
      // 直接侦听一个 ref
      const count = ref(0)
      watch(count, (count, prevCount) => {
        /* ... */
      })
      ​
      // 侦听多个源
      watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
        /* ... */
      })
      

watcheffect

  • 我觉得 主要是对 watch 进行一些补充和升级吧 vue3 中出现的新的 响应式 api

  • 说说区别 只知道进行了哪些补充了

    • watchEffect 不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行,而 watch 只能监听指定的属性而做出变更(v3开始可以同时指定多个)

    • watch 可以获取到新值与旧值(更新前的值),而 watchEffect 是拿不到的

      • 这个怎么感觉更拉了啊
    • watchEffect 如果存在的话 在组件初始化的时候就会执行一次用以收集依赖(与computed同理)而后收集到的依赖发生变化 这个回调才会再次执行 而 watch 不需要 因为他一开始就指定了依赖

  • 写法

    • const count = ref(0)
      ​
      watchEffect(() => console.log(count.value))
      

停止侦听

  • watchEffect 在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
  • watchEffect 会返回一个用于停止这个监听的函数,如法如下:
    ​
    const stop = watchEffect(() => {
      /* ... */
    })
    ​
    // later
    stop()
    

清除副作用

  • 有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:

    • 副作用即将重新执行时
    • 侦听器被停止 (如果在 setup() 或生命周期钩子函数中使用了 watchEffect,则在组件卸载时)
    watchEffect(onInvalidate => {
      const token = performAsyncOperation(id.value)
      onInvalidate(() => {
        // id has changed or watcher is stopped.
        // invalidate previously pending async operation
        token.cancel()
      })
    })
    

总结

  • computed

    • 解决自动更新数据的问题 替代方法

      • 主要区别是具有缓存性 依赖不变时不会重新计算
    • 简化模板中的复杂表达式

    • 默认的是 get 函数 返回一个只读的响应式对象 也可以自定义添加一个 set 方法

    • 最重要的是 关注派生出来的返回值

      • 简单地说 只要数据变化 我就会得到一个新的返回值去使用
  • watch

    • 传入一个 get 函数 之后执行一个回到函数

    • 最重要的是 不关注返回值 而关注于侦听数据变化 执行回调函数

      • 简单地说就是 只要数据变化 我想做一些其他的事情(回调函数)
    • watch 是惰性执行 也就是只有监听的值发生变化的时候才会执行 但是watchEffect不同,每次代码加载watchEffect都会执行

      • 第一次不执行 但是可以通过属性控制 immediate
    • watch 只能监听响应式数据:ref 定义的属性和 reactive 定义的对象,如果直接监听 reactive 定义对象中的属性是不允许的,除非使用函数转换一下

  • watchEffect

    • watchEffect 如果监听 reactive 定义的对象是不起作用的 只能监听对象中的属性

    • watchEffect 不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行,而 watch 只能监听指定的属性而做出变更(v3开始可以同时指定多个)

    • watch 可以获取到新值与旧值(更新前的值),而 watchEffect 是拿不到的

      • 这个怎么感觉更拉了啊
    • watchEffect 如果存在的话 在组件初始化的时候就会执行一次用以收集依赖(与computed同理)而后收集到的依赖发生变化 这个回调才会再次执行 而 watch 不需要 因为他一开始就指定了依赖

    • 还有一些进阶内容