Vue3 中 watch 属性解析

206 阅读5分钟

一、watch 属性是干啥的

在 Vue3 里,watch 属性就像是一个小侦探,专门盯着数据的变化。生活中,我们可能会关注股票价格的变动,一旦价格变了,就考虑是买入还是卖出。在 Vue3 的世界里,当某个数据发生变化时,我们可能需要执行一些操作,比如发送一个网络请求、更新页面上的某个元素,这时候 watch 就派上用场啦。它能帮助我们实现这些副作用逻辑,也就是在数据变化时要做的额外事情。

二、源码分析

简化源码示例

// 从响应式模块引入一些必要的工具函数
import { effect, reactive, track, trigger } from './reactive'

// 定义watch函数,接收两个参数:source和cb
function watch(source, cb) {
    // 用来存储旧值和新值
    let oldValue, newValue
    // 当数据变化时会执行的函数
    const job = () => {
        // 获取新值
        newValue = effect(() => source())
        // 如果旧值和新值不一样
        if (oldValue!== newValue) {
            // 调用回调函数,把旧值和新值传进去
            cb(oldValue, newValue)
            // 更新旧值为新值
            oldValue = newValue
        }
    }
    // 先获取一次初始值,作为旧值
    oldValue = effect(() => source())
    // 返回一个清理函数,现在里面暂时没写具体清理逻辑
    return () => {
        // 这里可以添加清理逻辑,比如取消订阅等
    }
}

// 创建一个响应式对象
const state = reactive({ count: 0 })

// 使用watch来观察state.count的变化
watch(() => state.count, (oldCount, newCount) => {
    console.log(`Count changed from ${oldCount} to ${newCount}`)
})

// 改变count的值,触发watch的回调
state.count++

功能解释

watch 函数就像是一个指挥官,它有两个重要的 “手下”:

  • source:这是一个 “情报收集员”,它会告诉 watch 要观察哪个数据。它可以是一个函数,这个函数返回我们要观察的数据。
  • cb:这是一个 “行动执行者”,当 source 所观察的数据发生变化时,cb 就会被调用,并且会得到变化前后的值,它可以根据这些值去执行相应的操作。

watch 函数还会返回一个清理函数,就像是一个 “收尾工人”,当我们不再需要这个 watch 时,可以用这个清理函数来做一些清理工作,比如取消订阅某个事件,避免占用不必要的资源。

工作原理

  1. 建立初始状态watch 函数一开始会调用 effect 函数(这个函数就像是一个 “魔法助手”,能帮助我们追踪数据的变化),让它去执行 source 函数,得到数据的初始值,把这个值记为 oldValue。在这个过程中,effect 会在 watch 和 source 函数用到的数据之间建立一种 “联系”,就像给它们牵了一根线,这样数据一变,watch 就能知道。
  2. 数据变化触发:当被观察的数据发生变化时(这个变化是通过 trigger 函数触发的,就像是有人拉了一下那根线),job 函数就会被调用。job 函数会再次让 effect 去执行 source 函数,得到新的值,记为 newValue
  3. 比较并执行操作:然后 watch 会比较 oldValue 和 newValue,如果它们不一样,就说明数据真的变了,这时候就会调用 cb 函数,把 oldValue 和 newValue 告诉它,让它去执行相应的操作。最后,把 oldValue 更新成 newValue,为下一次数据变化做准备。

示例代码解释

import { effect, reactive, track, trigger } from './reactive'

function watch(source, cb) {
    let oldValue, newValue
    const job = () => {
        newValue = effect(() => source())
        if (oldValue!== newValue) {
            cb(oldValue, newValue)
            oldValue = newValue
        }
    }
    oldValue = effect(() => source())
    return () => {
        // 这里可以添加清理逻辑,比如取消订阅等
    }
}

const state = reactive({ count: 0 })

watch(() => state.count, (oldCount, newCount) => {
    console.log(`Count changed from ${oldCount} to ${newCount}`)
})

state.count++
  1. 导入工具:代码开头导入了一些工具函数,就像是我们做菜需要的各种调料,这些函数是实现响应式的基础。
  2. 定义 watch 函数:我们自己定义了 watch 函数,让它能完成观察数据变化的任务。
  3. 创建响应式对象:用 reactive 函数创建了一个响应式对象 state,里面有个 count 属性,初始值是 0。这就像是我们有了一个小盒子,里面装着一个数字。
  4. 使用 watch 观察:用 watch 函数来观察 state.count 的变化。当 state.count 变了,就会在控制台打印出变化前后的值。
  5. 触发变化:最后让 state.count 自增 1,这就像是我们动了一下那个小盒子里的数字,watch 就会发现这个变化,然后调用回调函数,我们就能在控制台看到输出啦。

三、设计选择讨论

用 effect 建立依赖关系

就像盖房子需要一个好的地基一样,watch 使用 effect 函数来建立依赖关系,这是一个很聪明的设计。有了 effectwatch 能自动追踪数据的变化,我们不用手动去管理这些复杂的关系,代码变得简单又高效。如果没有 effect,我们可能要写很多额外的代码来处理数据的追踪,那可就麻烦死了。

返回清理函数

想象一下,我们举办了一场派对,派对结束后需要打扫场地。watch 返回的清理函数就像是打扫场地的工具。在实际开发中,有时候我们不再需要某个 watch 了,比如一个页面关闭了,对应的 watch 也应该停止工作。有了清理函数,我们就可以在里面写代码,比如取消订阅某个事件,避免占用不必要的内存,让程序运行得更流畅。

比较新旧值

这就像是我们去超市买东西,结账时会核对价格,看看是不是和标签上的一样。watch 比较 oldValue 和 newValue,只有当它们不一样时才会调用回调函数。如果不比较,哪怕数据变化前后的值是一样的,回调函数也会被调用,这就会浪费计算机的资源,让程序变慢。通过比较,我们只在数据真的有变化时才执行操作,提高了程序的性能。

通过上面的分析,相信你对 Vue3 里的 watch 属性有了更清楚的认识啦!