了解 vue composition-api 原理

写在前面的心得

  1. 1.

    不要在 setup 内直接使用 setInterval 与 setTimeout 这类需要手动清理副作用的 api

    1. 1.

      因为人很容易忘记清理他的副作用,应该自行封装一个类似于计算属性的

watch

我对 composition-api 中的 watch 的实现一直很好奇,它到底怎么知道我里面依赖了哪些变量?

例如下面的代码:

import {watchEffect, ref } from "vue"
const msg = ref("后撤步!")
watchEffect(()=>{
  console.log(msg.value)
})
setTimeout(()=>{
    msg.value = "7777"
},1000)

虽然用了这么久的 composition-api 但我从来都不明白 watchEffect 到底是怎么知道 cb 依赖了 msg 的

我自己猜想的方式有两种:

  • 第一种是直接执行一遍 cb, msg 的 getter 内就能知道 cb 依赖了 msg,但 cb 内如果有 if 之类的逻辑就会漏掉一些变量的依赖

    const t = Date.now()
    watchEffect(()=>{
      if(Date.now() - t > 500){
        console.log(msg.value) // 永远不会被执行
      }
      setTimeout(()=>{
        console.log(msg.value) // 只会被执行一次
      },10)
    })
    setTimeout(()=>{
        msg.value = "7777"
    },1000)
    

    像这样的代码在 1000ms 后并不会打印 msg.value

  • 另一种是获取 cb 的源码来进行分析,执行 cb.toString() 得到 ()=>{ console.log(msg) } 就可以看到里面依赖了 msg ,但如果将 cb 改一下

    const a = ()=>{
      console.log(msg)
    }
    const cb = ()=>{
      a()
    } 
    

    这时候 cb.toString() 得到的是 ()=>{ a() } 还是没法知道里面依赖了 msg。

经过对 此处代码 的验证,可以猜测 vue 应该是采用的第一种方案,还是去看 vuejs/composition-api 的代码来研究一下吧

正文

watchEffect 的实现在 src/apis/watch.ts#L368 , 代码如下

export function watchEffect(
  effect: WatchEffect,
  options?: WatchOptionsBase
): WatchStopHandle {
  const opts = getWatchEffectOption(options)
  const vm = getWatcherVM()
  return createWatcher(vm, effect, null, opts)
}

这里没啥好看的,继续看 createWatcher : src/apis/watch.ts#L205 , 代码如下(精简了代码)

function createWatcher(
  vm: ComponentInstance,
  source: WatchSource<unknown> | WatchSource<unknown>[] | WatchEffect,
  cb: WatchCallback<any> | null,
  options: WatchOptions
): () => void {

  let running = false
  const getter = () => {
      // preventing the watch callback being call in the same execution
      if (running) {
        return
      }
      try {
        running = true
        ;(source as WatchEffect)(registerCleanup)
      } finally {
        running = false
      }
    }
   const watcher = createVueWatcher(vm, getter, noopFn, {
      deep: options.deep || false,
      sync: isSync,
      before: runCleanup,
   })
}

这里可以看到 16行的 (source as WatchEffect)(registerCleanup) 基本验证了猜测,继续看 createVueWatcher : src/apis/watch.ts#L170

https://www.typescriptlang.org/zh/play?#code/DYUwLgBAJiAOBcEBKIBmAeArgOwNbYHsB3bAPgG0BdCAXgioFgAoAYwOwGdIZYAZASzy0I2EEQgB1EAENcAWWmx0KDDnzEyAGggBlcOgBiOFmH7tSpABQBKZszABPWCGRp0AFVLCA3hABu0sCYIIjuEAC+zKjGpuwQAE5unpZ+odaIKh5e3swQeRCgkAFBLnR+ANy5+YlgmPHYEDlM+S0QHOD+gcEpaY1Vra3FwcJ+-QP5bJyQLABGAlzCPAJ4AHQA5uCWYAAW-By2zeMt-KgQlrPzYNZ9h0cDF3tgK6gE8QCi0izb5zO0XrM2A53fKRW6tcKaMYtDZFLogGyhG7AvI8FawTAcb47PZA5E1OoNIYgKEgsag0FRGJmBpsAC26LAICgWUsMMZ8UQNj+EHc6VcGE8SIm7AWROEbJA8RsY0mCzpDKZKmEiVQKThuJRcGerw+X0sPG5TSOhQgDwWdCWglw602PA1rROZwAhGarkLga7hKJxHowNKwUdLat2n6eNpXfaWqCjq6VtIoFBLFyaNkSfcCPTMIyoCoVmK6BKpZGQfbwvaDXRGGD8fVTRmFTm0MwKawRZBpMq0JYAMwHWXTDt0eVZplJ64p911zgEUArYAENaWaR59VjGsNZdE5sHZgh9z8WkgAhZseGsabuHCADsze0AEYAAxPg57g9Hk-J1Ngi8lYQADhlEUZxAOcF3OH9ggOCEIAAJifB8X3AABJbB2WKU8JyNfIIJAABqXDbwgbt4OsIA

未完待续

by 崮生 from 崮生 • 一些随笔 🎨,欢迎 赞助本文
本文欢迎分享与聚合,全文转载未经授权( 联系我)不许可。