浅曦Vue源码分析-37-挂载阶段-$mount-(25)渲染watcher(1)
2024-02-01 14:11:21
导语
在上一篇我们已经分析完了 mount 方法的核心方法 mountComponent,接下来我们就来看看 mountComponent 方法中是如何创建渲染 watcher 的,以及 Vue 如何通过渲染 watcher 收集模板中的依赖的。
mountComponent 方法创建渲染 watcher
mountComponent (
component: ComponentInternalInstance | null,
hydrating?: boolean
): void {
component.update = () => {
if (!component.isMounted) {
callHook(component, 'beforeMount')
const updateComponent = () => {
const { render, data } = component
if (!render) {
// no render function found
if (process.env.NODE_ENV !== 'production') {
warn(`getComponentRootNode(): must specify a render function.`);
}
return
}
// extract render result before component update
component.renderContext = new Proxy(component, componentRenderContextProxyHandlers)
// update component state
if (// force update
!hydrating) {
component.preProcessRender()
if (component.parent) {
component.parent.renderUpdated = true
}
}
const vnode = component.render()
// merge vnodes if necessary
if (component.renderContext.parent && component.renderContext.parent.data) {
// inherit parent template's slot scope
component.renderContext.parent.data.slot = vnode
if (vnode) {
vnode.parent = component.renderContext.parent
}
}
component.renderResult = vnode
// clear old tree
component.tree = component.patchFlag === -1 // optimize patch
? null
: oldVnode(component.vnode)
component.vnode = vnode
// install component root contents
if (component.parent) {
component.parent.update()
}
}
const mountedVNode = component._parentVnode
if (mountedVNode) {
renderComponentRoot(component, mountedVNode, updateComponent)
} else {
if (process.env.NODE_ENV !== 'production') {
warn(
`mountComponent(): parent component must exist.`,
component
)
}
}
if (component.componentDidMount) {
component.$nextTick(() => {
component.componentDidMount()
})
}
component.isMounted = true
callHook(component, 'mounted')
} else {
// update
let vnode = component.render()
const prevVnode = component.vnode
component.vnode = vnode
// Vue2 will check if the returned value is VNode
// Vue3 will check if the returned value is VNode or null
if (component.renderUpdated) {
component.renderUpdated = false
component.update()
} else {
component.patch(prevVnode, vnode)
}
}
}
// create reactive effect for rendering
component.updateRenderer = createVNodeWachter(component, component.render)
// component finished mounting
if (process.env.NODE_ENV !== 'production') {
component.mounted = true
}
component.beforeUpdate = () => {
component.renderUpdated = true
}
component.updateRenderer()
}
在 mountComponent 方法中,我们首先定义了一个 update 方法,这个方法是组件更新的核心逻辑。在 update 方法中,我们首先会调用 beforeMount 钩子函数,然后我们提取出组件的 render 函数和 data 对象。接下来,我们调用 preProcessRender 方法对组件状态进行更新,然后调用 render 方法渲染组件。
如果组件的父组件存在,我们就会把渲染结果合并到父组件的 vnode 中。然后,我们将渲染结果存储在组件的 renderResult 属性中,并清除旧的树。最后,我们将新的 vnode 存储在组件的 vnode 属性中,并调用 update 方法更新父组件。
如果组件的父组件不存在,我们就会输出警告信息,并终止后续操作。
如果组件有 componentDidMount 钩子函数,我们就会在组件下次更新时调用它。然后,我们将组件的 isMounted 属性设置为 true,并调用 mounted 钩子函数。
在 update 方法的最后,我们创建了一个渲染 watcher,并把它存储在组件的 updateRenderer 属性中。这个渲染 watcher将在组件更新时被触发,并重新渲染组件。
Vue 如何通过渲染 watcher 收集模板中的依赖
Vue 通过渲染 watcher 收集模板中的依赖,以便在这些依赖发生变化时重新渲染组件。渲染 watcher 是一个特殊的 watcher,它会在组件更新时被触发,并重新渲染组件。
在创建渲染 watcher 时,Vue 会使用一个特殊的 Proxy 对象来包装组件的 render 函数。这个 Proxy 对象会在组件的 render 函数被调用时拦截对组件状态的访问,并记录下这些访问。当组件的状态发生变化时,Vue 会通过 Proxy 对象来检测到这些变化,并触发渲染 watcher。
渲染 watcher 在被触发时,会重新调用组件的 render 函数,并把渲染结果与之前的渲染结果进行比较。如果渲染结果发生了变化,Vue 就会把新的渲染结果更新到组件的 DOM 中。
通过这种方式,Vue 可以确保组件的状态与 DOM 中的内容始终保持一致。
结语
以上就是 Vue 如何通过渲染 watcher 收集模板中的依赖的原理。希望这篇小作文能对你有帮助。