Vue 源码-渲染函数

62 阅读1分钟

vue版本:v2.7.10

src/platforms/web/runtime/index.ts

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

export function mountComponent(
  vm: Component,
  el: Element | null | undefined,
  hydrating?: boolean
): Component {
  vm.$el = el
  callHook(vm, 'beforeMount')
  
  const updateComponent = () => {
     vm._update(vm._render(), hydrating)
  }
  const watcherOptions: WatcherOptions = {
    before() {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }
  new Watcher(
    vm,
    updateComponent,
    noop,
    watcherOptions,
    true /* isRenderWatcher */
  )
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

看一下Vue元素挂载函数:$mount -> mountComponent 在mountComponent是创建了渲染Watcher,Watcher的回调更新函数是updateComponent,其实现是:vm._update(vm._render(), hydrating)。因为Vue组件元素渲染的核心在于vm._update和vm._render

vm._update

src/core/instance/lifecycle.ts

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    if (!prevVnode) {
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
  }

vm._update函数传入一个vnode实例,通过vm.__patch__进行元素更新

src/platforms/web/runtime/index.ts

Vue.prototype.__patch__ = inBrowser ? patch : noop

src/platforms/web/runtime/patch.ts

export const patch: Function = createPatchFunction({ nodeOps, modules })

接下来就是Vue核心的diff算法了,内容比较复杂,在Vue 源码-diff算法篇章详细描述。

vm._render

src/core/instance/render.ts

Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options
    ...
    vm.$vnode = _parentVnode!
    let vnode
    try {
      setCurrentInstance(vm)
      currentRenderingInstance = vm
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } finally {
      currentRenderingInstance = null
      setCurrentInstance()
    }
    ...
    vnode.parent = _parentVnode
    return vnode
  }

vm._render通过render.call(vm._renderProxy, vm.$createElement)创建一个vnode实例并返回,用于vm._update进行vnode的对比和更新。render函数的实现一般是h => h(App),因为最终的实现是vm.$createElement(APP),这里的APP是单文档组件导出的组件选项对象。

vm.$createElement 将在Vue源码-VNode篇章详细介绍。

而单文件组件的导出内容得通过vue-loader文档进行学习。