vue3的watch原理

117 阅读1分钟

1、执行下面代码

packages\vue\examples\composition\test.html

<script src="../../dist/vue.global.js"></script>
<div id="demo">
  <h1>
    <header>{{count}}</header>
  </h1>
</div>
<script>
  const { createApp, ref, toRefs, reactive, watch, onMounted, computed } = Vue
  var app = createApp({
    setup() {
      var count = ref(1)
      setTimeout(() => {
        count.value++
      }, 1000);
      watch(()=>count.value,(x,y)=>{
        debugger
        console.log(x,y);
      })
      return { count }
    }
  })
  app.mount('#demo')
</script>

2、代码分析

packages\runtime-core\src\apiWatch.ts

  1. setupComponent时,执行setup函数,执行watch函数
  2. 进一步执行doWatch函数
  3. 获取用户传的getter,和cb函数
  4. 构建job函数
    1. const newValue = effect.run()
    2. 如果hasChanged(newValue, oldValue))true,则
    3. 执行cb函数,且oldValue = newValue
  5. const effect = new ReactiveEffect(getter, () => { queuePreFlushCb( job ) })
  6. 首次执行oldValue = effect.run()
  7. 触发上面获取的getter函数,于是触发了响应式数据的track函数取关联当前的effect
  8. 当响应式数据发生变化时,触发函数() => { queuePreFlushCb( job ) }
function watch(
  source,
  cb,
  options
){
  return doWatch(source, cb, options)
}
function doWatch(
  source,
  cb,
  { immediate, deep, flush, onTrack, onTrigger }
){
  const instance = currentInstance
  let getter
  if (isFunction(source)) {
      getter = () => callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)  
  }
  let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE
  const job = () => {
     const newValue = effect.run()
     if ( hasChanged(newValue, oldValue))  ) {
       callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
         newValue,
         oldValue
       ])
       oldValue = newValue
     }
  }
  job.allowRecurse = !!cb
  const effect = new ReactiveEffect(getter, () => { queuePreFlushCb( job ) })
  if (immediate) {
    job()
  } else {
    oldValue = effect.run()
  }
  return () => {
    effect.stop()
    if (instance && instance.scope) {
      remove(instance.scope.effects, effect)
    }
  }
}
export function queuePreFlushCb(cb) {
  queueCb(cb, activePreFlushCbs, pendingPreFlushCbs, preFlushIndex)
}
function queueCb(
  cb,
  activeQueue,
  pendingQueue,
  index
) {
  if (!isArray(cb)) {
    if (
      !activeQueue ||
      !activeQueue.includes(cb, cb.allowRecurse ? index + 1 : index)
    ) {
      pendingQueue.push(cb)
    }
  } else {
    pendingQueue.push(...cb)
  }
  queueFlush()
}
function queueFlush() {
  if (!isFlushing && !isFlushPending) {
    isFlushPending = true
    currentFlushPromise = resolvedPromise.then(flushJobs)
  }
}
const resolvedPromise = Promise.resolve()
function flushJobs(seen) {
  isFlushPending = false
  isFlushing = true
  flushPreFlushCbs(seen)
  queue.sort((a, b) => getId(a) - getId(b))
  const check =  NOOP
  try {
    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
      const job = queue[flushIndex]
      if (job && job.active !== false) {
        callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
      }
    }
  } finally {
    flushIndex = 0
    queue.length = 0
    flushPostFlushCbs(seen)
    isFlushing = false
    currentFlushPromise = null
    if (
      queue.length ||
      pendingPreFlushCbs.length ||
      pendingPostFlushCbs.length
    ) {
      flushJobs(seen)
    }
  }
}

processOn