前言
在 Vue 核心中除了响应式原理外,视图渲染也是重中之重。我们都知道每次更新数据,都会走视图渲染的逻辑,而这当中牵扯的逻辑也是十分繁琐。 本文主要解析的是初始化视图渲染流程,你将会了解到从挂载组件开始, Vue 是如何构建 VNode ,又是如何将 VNode 转为真实节点并挂载到页面。
挂载组件($mount)
Vue 是一个构造函数,通过 new 关键字进行实例化。
// src/core/instance/index.jsfunction Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
在实例化时,会调用 _init 进行初始化。
// src/core/instance/init.js
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// ...if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
_init 内会调用 来挂载组件,而mount 方法实际调用的是 mountComponent 。
// src/core/instance/lifecycle.jsexport function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// ...
callHook(vm, 'beforeMount')let updateComponent
/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
// ...
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating) // 渲染页面函数
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, { // 渲染watcher
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
mountComponent 除了调用一些生命周期的钩子函数外,最主要是 updateComponent ,它就是负责渲染视图的核心方法,其只有一行核心代码:
vm._update(vm._render(), hydrating)
vm._render 创建并返回 VNode , vm._update 接受 VNode 将其转为真实节点。 updateComponent 会被传入 渲染Watcher ,每当数据变化触发 Watcher 更新就会执行该函数,重新渲染视图。updateComponent 在传入 渲染Watcher 后会被执行一次进行初始化页面渲染。 所以我们着重分析的是 vm._render 和 vm._update 两个方法,这也是本文主要了解的原理—— Vue 视图渲染流程。
构建VNode(_render)
首先是 _render 方法,它用来构建组件的 VNode 。
// src/core/instance/render.js
Vue.prototype._render = function () {
const { render, _parentVnode } = vm.$options
vnode = render.call(vm._renderProxy, vm.$createElement)return vnode
}
_render 内部会执行 render 方法并返回构建好的 VNode 。render 一般是模板编译后生成的方法,也有可能是用户自定义。
// src/core/instance/render.jsexport function initRender (vm) {
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}
initRender 在初始化就会执行为实例上绑定两个方法,分别是 vm._c 和 vm.$createElement 。它们两者都是调用 createElement 方法,它是创建 VNode 的核心方法,最后一个参数用于区别是否为用户自定义。 vm._c 应用场景是在编译生成的 render 函数中调用, vm.createElement ,我们在自定义 render 函数接收到的参数就是它。
createElement
// src/core/vdom/create-elemenet.jsexport function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}return _createElement(context, tag, data, children, normalizationType)
}
createElement 方法实际上是对 _createElement 方法的封装,它允许传入的参数更加灵活。
export function _createE