源码Watcher类型
Render Watcher
src/platforms/web/runtime/index.ts - 通过$mount绑定,后续还会在complier增强
- 进行$mount的挂载,调用mountComponent函数,进行元素获取
typescript
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钩子
javascript
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函数中使用
typescriptconst 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
scssfunction 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
typescriptfunction 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清理函数
typescript
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
函数。目的是避免覆盖实例上已经存在的属性
javascript
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的抽象类
typescriptexport interface DebuggerOptions { onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void }
-
src/core/observer/dep.ts Watcher的抽象类 DepTarget
javaexport interface DepTarget extends DebuggerOptions { id: number addDep(dep: Dep): void update(): void }
-
Watcher实现
recordEffectScope
的作用是 将副作用(Effect,如 Watcher)与当前的作用域(如组件实例)关联,实现依赖的自动清理和资源管理- 根据配置判断等待执行还是立即执行getter
- 使用traverse深度访问响应式数据,触发深度的getter
dirty
用于实现计算属性的缓存。当依赖的响应式数据未变化时,直接返回缓存值,避免重复计算- Watcher类实现 addDep update, 提供给dep类调用
kotlinimport { 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() } } } }