在之前mount实例的挂载的学习中,已经知道了mount挂载过程中最后调用了vm._update(vm._render(), hydrating)
来将我们的Vue对象变成真实的DOM。
这一行代码中,hydrating先忽略,这个参数和服务器渲染相关。主要的就是render
和update
这两个方法。
render的执行过程
在我们新建一个Vue对象的时候,进行了很多初始化的工作。
在src/core/instance/index.js文件中使用renderMixin(Vue)
来定义了Vue.prototype._render
。该方法的返回参数是一个vnode也就是Virtual DOM。这个vnode怎么来的呢?
在函数定义中可以看到存在这么一句语句:vnode = render.call(vm._renderProxy, vm.$createElement)。
render就是我们在创建Vue对象的时候自己定义的render函数。如下面的例子:
new Vue({
el: '#app',
render: function (createElement) {
return createElement('div', this.message)
},
data: {
message: 'Hello world!'
}
})
vm._renderProxy在生产环境中就是Vue实例,在非生产环境中,会使用Proxy进行代理,对一些错误进行提示。第二个参数则是在我们自定义render函数时候的一个方法。
在initRender的时候,会初始化以下两行代码:
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)
第一行是通过模版编译或得到render函数的时候使用,第二个则是我们自定义render函数的时候使用。但本质上都调用了createElement这个方法。
createElement
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
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
方法做了一下数据的处理:
在传递参数的时候,data可能存在为空的情况,当data满足第一个if中的条件的时候,则说明data为空,通过参数的前移获取真正对应的参数。之后将处理后的参数传递进入_createElement
。
以上面定义的例子进行单步调试:
这个例子在_createElement调用的时候参数比较有限,运行的分支逻辑也比较简单。主要进行了两步操作:
-
将children属性标准化,将数组拍平成为一维数组
if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children); } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children); }
-
创建一个vnode
vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context );
最后返回的这个vnode就是render函数返回的vnode。
update的执行过程
update的调用时机分为两个:
- 初始渲染的时候
- 数据变更→视图更新
这里先看初始渲染的过程。
在src/core/instance/lifecycle.js里面定义Vue.prototype._update
。由于是初始渲染的情况,prevNode是不存在的,就调用了vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
这一方法。
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
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
通过单步调试来查看一下进入该方法的时候的初始化参数的情况:
参数基本都为undefined,主要就执行了vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
。patch也是在Vue对象初始化的时候引入的。
patch相关的定义在src/platforms/web/runtime/patch.js文件中:
const patch: Function = createPatchFunction({ nodeOps, modules })
该文件中内容相对较少,patch就是createPatchFunction返回的函数。nodeOps是操作真实dom的方法。这里通过函数柯里化的方式在一开始将平台差异抹平,减少后续操作的分支处理。createPatchFunction返回了一个patch方法。该返回的patch方法中和真实dom相关的就是createElm方法。
patch
进入patch的一些参数:
由于本次不使用服务器渲染,第一部分只执行了oldVnode = emptyNodeAt(oldVnode);来将从文档中获取的dom节点转化为vnode。patch方法通过递归构建子元素,然后将子元素插入父元素,最后将创建好的元素插入到真实的DOM树中,在这一步之后,我们可以看到浏览器中的DOM就已经渲染好了。
总结
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 小馒头儿 原文链接:https://juejin.im/post/6925360511655657480