vue2.7.16源码 - Watcher

45 阅读3分钟

源码Watcher类型

Render Watcher

src/platforms/web/runtime/index.ts - 通过$mount绑定,后续还会在complier增强

  • 进行$mount的挂载,调用mountComponent函数,进行元素获取
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

src/core/instance/lifecycle.ts - mountComponent方法

  • 触发生命周期 beforeMount
  • 绑定更新函数,在更新函数执行render方法,这个render方法使用的是compiler绑定的
  • 处理存储的Watcher
  • 触发mounted钩子
export function mountComponent(
  vm: Component,
  el: Element | null | undefined,
  hydrating?: boolean
): Component {
  vm.$el = el
 
  callHook(vm, 'beforeMount')

  let updateComponent = () => {
      vm._update(vm._render(), hydrating)
  }
  const watcherOptions: WatcherOptions = {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }
 // 创建Watcher  传入当前组件实例 和 更新方法以及配置项
  new Watcher(
    vm,   // 实例
    updateComponent, // 更新回调
    noop,   //  render Watcher用不到,是回调函数
    watcherOptions,  // 配置项
    true /* isRenderWatcher */
  )
  hydrating = false  // 重置

  // 获取预先存储的 Watcher 列表
  // 处理父组件传递的动态 props 和插槽内容
  const preWatchers = vm._preWatchers
  if (preWatchers) {
    for (let i = 0; i < preWatchers.length; i++) {
      preWatchers[i].run()
    }
  }

  // 触发mounted
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

src/platforms/web/runtime-with-compiler.ts - compiler重写$mount

  • 在compiler层处理render函数和目标节点,然后将这些信息传递给原始的mounted

  • hydrating 表示是否要复用dom节点,在patch函数中使用

    • const idToTemplate = cached(id => {
        const el = query(id)
        return el && el.innerHTML
      })
      
      function getOuterHTML(el: Element): string {
        if (el.outerHTML) {
          return el.outerHTML
        } else {
          const container = document.createElement('div')
          container.appendChild(el.cloneNode(true))
          return container.innerHTML
        }
      }
      
      const mount = Vue.prototype.$mount
      Vue.prototype.$mount = function (
        el?: string | Element,
        hydrating?: boolean
      ): Component {
        el = el && query(el)
      
        // dom目标不能是body 和 html
        if (el === document.body || el === document.documentElement) {
          __DEV__ &&
            warn(
              `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
            )
          return this
        }
      
        const options = this.$options
        
        // 第一次构建render
        if (!options.render) {
        // 获获取模板信息
          let template = options.template
          if (template) {
            if (typeof template === 'string') {
              if (template.charAt(0) === '#') {
              // 获取目标的innerHTML
                template = idToTemplate(template)
                /* istanbul ignore if */
                if (__DEV__ && !template) {
                  warn(
                    `Template element not found or is empty: ${options.template}`,
                    this
                  )
                }
              }
            } else if (template.nodeType) {
            // 如果是html节点 获取innerHTML
              template = template.innerHTML
            } else {
              if (__DEV__) {
                warn('invalid template option:' + template, this)
              }
              return this
            }
          } else if (el) {
            // 创建一个目标节点返回其html内容
            template = getOuterHTML(el)
          }
          if (template) {
         
           // 将html模板转为render函数
            const { render, staticRenderFns } = compileToFunctions(
              template,
              {
                outputSourceRange: __DEV__,
                shouldDecodeNewlines,
                shouldDecodeNewlinesForHref,
                delimiters: options.delimiters,
                comments: options.comments
              },
              this
            )
            options.render = render
            options.staticRenderFns = staticRenderFns
          }
        }
        return mount.call(this, el, hydrating)
      }
      

Watch Watcher

src/core/instance/state.ts - initState中,触发initWatch

  • 传入的watch数组如果是数组需要遍历创建Watcher

    • function initWatch(vm: Component, watch: Object) {
        for (const key in watch) {
          const handler = watch[key]
          if (isArray(handler)) {
            for (let i = 0; i < handler.length; i++) {
              createWatcher(vm, key, handler[i])
            }
          } else {
            createWatcher(vm, key, handler)
          }
        }
      }
      
  • 如果传入handle是一个平面对象,需要拆出handler,重新调用$watch

    • function createWatcher(
        vm: Component,
        expOrFn: string | (() => any),
        handler: any,
        options?: Object
      ) {
        if (isPlainObject(handler)) {
          options = handler
          handler = handler.handler
        }
        if (typeof handler === 'string') {
          handler = vm[handler]
        }
        return vm.$watch(expOrFn, handler, options)
      }
      

src/core/instance/state.ts - 绑定$watch

  • 拆解cb中的handler
  • 创建Watcher
  • 如果是immediate需要先执行一边Watcher
  • 返回Watcher清理函数
  Vue.prototype.$watch = function (
    expOrFn: string | (() => any),
    cb: any,
    options?: Record<string, any>
  ): Function {
    // 拆解cb 会重新执行$watch
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn() {
      watcher.teardown()
    }
  }
}

Computed Watcher

src/core/instance/state.ts - initState中,触发initComputed

  • 读取computed配置,生成Watcher
  • 如果不存在,就调用defineComputed函数。目的是避免覆盖实例上已经存在的属性
const computedWatcherOptions = { lazy: true }

function initComputed(vm: Component, computed: Object) {
  // 保存 _computedWatchers
  const watchers = (vm._computedWatchers = Object.create(null))
  
  const isSSR = isServerRendering()

// 遍历computed配置,按顺序生成computed Watcher
  for (const key in computed) {
    const userDef = computed[key]
    const getter = isFunction(userDef) ? userDef : userDef.get
 
    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,   // computed副作用函数
        noop,
        computedWatcherOptions  // 专属配置
      )
    }

    // 如果是一个新的值,要变成响应式的值
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } 
    }
  }
}

Watcher源码

源码地址 - src/core/observer/watcher.ts

  • src/v3/debug.ts - DepTarget的抽象类

    • export interface DebuggerOptions {
        onTrack?: (event: DebuggerEvent) => void
        onTrigger?: (event: DebuggerEvent) => void
      }
      
  • src/core/observer/dep.ts Watcher的抽象类 DepTarget

    • export interface DepTarget extends DebuggerOptions {
        id: number
        addDep(dep: Dep): void
        update(): void
      }
      
  • Watcher实现

    • recordEffectScope 的作用是 将副作用(Effect,如 Watcher)与当前的作用域(如组件实例)关联,实现依赖的自动清理和资源管理
    • 根据配置判断等待执行还是立即执行getter
    • 使用traverse深度访问响应式数据,触发深度的getter
    • dirty 用于实现计算属性的缓存。当依赖的响应式数据未变化时,直接返回缓存值,避免重复计算
    • Watcher类实现 addDep update, 提供给dep类调用
    • import {
        warn,
        remove,
        isObject,
        parsePath,
        _Set as Set,
        handleError,
        invokeWithErrorHandling,
        noop,
        isFunction
      } from '../util/index'
      
      import { traverse } from './traverse'
      import { queueWatcher } from './scheduler'
      import Dep, { pushTarget, popTarget, DepTarget } from './dep'
      import { DebuggerEvent, DebuggerOptions } from 'v3/debug'
      
      import type { SimpleSet } from '../util/index'
      import type { Component } from 'types/component'
      import { activeEffectScope, recordEffectScope } from 'v3/reactivity/effectScope'
      
      let uid = 0
      
      /**
       * @internal
       */
      export interface WatcherOptions extends DebuggerOptions {
        deep?: boolean
        user?: boolean
        lazy?: boolean
        sync?: boolean
        before?: Function
      }
      
      /**
        *观察者解析表达式,收集依赖项,
        *并在表达式值改变时触发回调。
        *这用于$watch() api和指令。
       * @internal
       */
      export default class Watcher implements DepTarget {
        vm?: Component | null
        expression: string
        cb: Function
        id: number
        deep: boolean
        user: boolean
        lazy: boolean
        sync: boolean
        dirty: boolean
        active: boolean
        deps: Array<Dep>   // 依赖数组
        newDeps: Array<Dep>  // 新的依赖数字
        depIds: SimpleSet
        newDepIds: SimpleSet
        before?: Function   // 更新前的回调
        onStop?: Function
        noRecurse?: boolean
        getter: Function
        value: any
        post: boolean
      
        // vue开发者使用
        onTrack?: ((event: DebuggerEvent) => void) | undefined
        onTrigger?: ((event: DebuggerEvent) => void) | undefined
      
        constructor(
          vm: Component | null,   //  组件实例
          expOrFn: string | (() => any),  // render Watcher的render函数
          cb: Function,                   // 回调函数
          options?: WatcherOptions | null,   // 配置
          isRenderWatcher?: boolean   //  标记是render Watcher
        ) {
          recordEffectScope(
            this,
            // if the active effect scope is manually created (not a component scope),
            // prioritize it
            activeEffectScope && !activeEffectScope._vm
              ? activeEffectScope
              : vm
              ? vm._scope
              : undefined
          )
          // 标记render目标
          if ((this.vm = vm) && isRenderWatcher) {
            vm._watcher = this
          }
          // 挂载配置项到实例
          if (options) {
            this.deep = !!options.deep
            this.user = !!options.user
            this.lazy = !!options.lazy
            this.sync = !!options.sync
            this.before = options.before
            if (__DEV__) {
              this.onTrack = options.onTrack
              this.onTrigger = options.onTrigger
            }
          } else {
              // 设置默认值
            this.deep = this.user = this.lazy = this.sync = false
          }
          this.cb = cb
          this.id = ++uid // 创建id
          this.active = true
          this.post = false
          this.dirty = this.lazy // for lazy watchers
          this.deps = []
          this.newDeps = []
          this.depIds = new Set()
          this.newDepIds = new Set()
          this.expression = __DEV__ ? expOrFn.toString() : ''
          // 获取getter的内容
          if (isFunction(expOrFn)) {
            this.getter = expOrFn
          } else {
            this.getter = parsePath(expOrFn)
            if (!this.getter) {
              this.getter = noop
              __DEV__ &&
                warn(
                  `Failed watching path: "${expOrFn}" ` +
                    'Watcher only accepts simple dot-delimited paths. ' +
                    'For full control, use a function instead.',
                  vm
                )
            }
          }
          // 延迟延迟获取 或者是 主动触发
          this.value = this.lazy ? undefined : this.get()
        }
      
        /**
         * 执行getter 并且重新收集依赖
         */
        get() {
          pushTarget(this)
          let value
          const vm = this.vm
          try {
            value = this.getter.call(vm, vm)
          } catch (e: any) {
            if (this.user) {
              handleError(e, vm, `getter for watcher "${this.expression}"`)
            } else {
              throw e
            }
          } finally {
            // 如果是深度监听 需要深度遍历 ,因为在前面变为响应了,需要访问一下触发一下get
            if (this.deep) {
              traverse(value)
            }
            popTarget()
            // 清空依赖 更新deps数组
            this.cleanupDeps()
          }
          return value
        }
      
        /**
         * 操作dep的增加
         */
        addDep(dep: Dep) {
          const id = dep.id
          if (!this.newDepIds.has(id)) {
            this.newDepIds.add(id)
            this.newDeps.push(dep)
            if (!this.depIds.has(id)) {
              dep.addSub(this)
            }
          }
        }
      
        /**
         * Clean up for dependency collection.
         */
        cleanupDeps() {
          let i = this.deps.length
          while (i--) {
            const dep = this.deps[i]
            if (!this.newDepIds.has(dep.id)) {
              dep.removeSub(this)
            }
          }
          let tmp: any = this.depIds
          this.depIds = this.newDepIds
          this.newDepIds = tmp
          this.newDepIds.clear()
          tmp = this.deps
          this.deps = this.newDeps
          this.newDeps = tmp
          this.newDeps.length = 0
        }
      
        /**
         * Subscriber interface.
         * Will be called when a dependency changes.
         */
        update() {
          /* istanbul ignore else */
          if (this.lazy) {
            this.dirty = true
          } else if (this.sync) {
            this.run()
          } else {
            queueWatcher(this)
          }
        }
      
        /**
         * Scheduler job interface.
         * Will be called by the scheduler.
         */
        run() {
          if (this.active) {
            const value = this.get()
            if (
              value !== this.value ||
              // Deep watchers and watchers on Object/Arrays should fire even
              // when the value is the same, because the value may
              // have mutated.
              isObject(value) ||
              this.deep
            ) {
              // set new value
              const oldValue = this.value
              this.value = value
              if (this.user) {
                const info = `callback for watcher "${this.expression}"`
                invokeWithErrorHandling(
                  this.cb,
                  this.vm,
                  [value, oldValue],
                  this.vm,
                  info
                )
              } else {
                this.cb.call(this.vm, value, oldValue)
              }
            }
          }
        }
      
        /**
         * Evaluate the value of the watcher.
         * This only gets called for lazy watchers.
         */
        evaluate() {
          this.value = this.get()
          this.dirty = false
        }
      
        /**
         * Depend on all deps collected by this watcher.
         */
        depend() {
          let i = this.deps.length
          while (i--) {
            this.deps[i].depend()
          }
        }
      
        /**
         * Remove self from all dependencies' subscriber list.
         */
        teardown() {
          if (this.vm && !this.vm._isBeingDestroyed) {
            remove(this.vm._scope.effects, this)
          }
          if (this.active) {
            let i = this.deps.length
            while (i--) {
              this.deps[i].removeSub(this)
            }
            this.active = false
            if (this.onStop) {
              this.onStop()
            }
          }
        }
      }