Vue组件初渲染
夏日 人气:0前言
在Vue
进行文本编译之后,会得到代码字符串生成的render
函数。本文会基于render
函数介绍以下内容:
- 执行
render
函数生成虚拟节点 - 通过
vm._update
方法,将虚拟节点渲染为真实DOM
在vm.$mount
方法中,文本编译完成后,要进行组件的挂载,代码如下:
Vue.prototype.$mount = function (el) { // text compile code .... mountComponent(vm); }; // src/lifecycle.js export function mountComponent (vm) { vm._update(vm._render()); }
下面详细介绍vm._render()
和vm._update()
中到底做了什么
生成虚拟节点
原生DOM
节点拥有大量的属性和方法,操作DOM
比较耗费性能。在Vue
中通过一个对象来描述DOM
中的节点,这个对象就是虚拟节点,Vue
组件树构建的整个虚拟节点树就是虚拟DOM
。
这是一段html
<div id="app"> <span>hello world {{name}}</span> </div> <script> new Vue({ el: '#app', data () { return { name: 'zs' } } }) </script>
其对应的虚拟节点如下:
const vNode = { tag: 'div', props: { id: 'app' }, key: undefined, children: [ { tag: 'span', props: {}, key: undefined, children: undefined, text: 'helloworldzs' } ], text: undefined }
在Vue.prototype._render
函数中,通过执行文本编译后生成的render
方法,会得到虚拟节点:
// src/vdom/index.js Vue.prototype._render = function () { const vm = this; // 执行选项中的render方法,指定this为Vue实例 const { render } = vm.$options; return render.call(vm); };
而render
函数中用到了_c
,_v
,_s
这些方法,需要在Vue.prototype
上添加这些方法,在render
函数内就可以通过实例调用它们:
// 创建虚拟节点 function vNode (tag, props, key, children, text) { return { tag, props, key, children, text }; } // 创建虚拟元素节点 function createVElement (tag, props = {}, ...children) { const { key } = props; delete props.key; return vNode(tag, props, key, children); } // 创建虚拟文本节点 function createTextVNode (text) { return vNode(undefined, undefined, undefined, undefined, text); } // 将实例中data里的值转换为字符串 function stringify (value) { if (value == null) { return ''; } else if (typeof value === 'object') { return JSON.stringify(value); } else { return value; } } export function renderMixin (Vue) { Vue.prototype._c = createVElement; Vue.prototype._v = createTextVNode; Vue.prototype._s = stringify; // some code ... }
_render
函数最终会递归的调用这些函数来得到虚拟节点,并将其返回:
const vNode = vm.createVElement('div', { id: 'app' }, vm.createVElement('span', undefined, vm.createTextVNode('hello') + vm.createTextVNode('world') + vm.stringify(vm.name) ) )
在生成虚拟节点的过程中,会从组件实例vm
中取值,从而触发对应属性的get/set
方法。
将虚拟节点处理为真实节点
在通过Vue.prototype._render
函数生成虚拟节点后,在Vue.prototype._update
方法中会利用虚拟节点,替换当前页面上渲染的元素app
。
其代码如下:
// src/lifecycle.js export function lifecycleMixin (Vue) { Vue.prototype._update = function (vNode) { const vm = this; patch(vm.$el, vNode); }; }
在patch
方法中,会通过虚拟节点创建真实节点,并将真实节点插入页面中:
// src/vdom/patch.js export function patch (oldVNode, vNode) { // 将虚拟节点创建为真实节点,并插入到dom中 const el = createElement(vNode); // 获取到老节点的父节点 const parentNode = oldVNode.parentNode; // 将新节点插入到老节点之后 parentNode.insertBefore(el, oldVNode.nextSibling); // 删除老节点 parentNode.removeChild(oldVNode); }
createElement
中是用虚拟节点生成真实节点的逻辑:
- 通过
document.createElement
来创建元素节点 - 元素节点通过
updateProperties
方法来设置它的属性 - 通过
document.createTextNode
来创建文本节点
function createElement (vNode) { if (typeof vNode.tag === 'string') { vNode.el = document.createElement(vNode.tag); updateProperties(vNode); for (let i = 0; i < vNode.children.length; i++) { const child = vNode.children[i]; vNode.el.appendChild(createElement(child)); } } else { vNode.el = document.createTextNode(vNode.text); } return vNode.el; }
createElement
会生成的真实DOM
元素el
并返回,内部会对子虚拟节点再次调用createElement
来继续生成真实元素,然后将生成的真实元素通过appendChild
方法插入到父节点中。
执行createElement
最后得到的el
是将所有子节点都插入到内部的元素,但其实el
此时还是脱离真实DOM
存在的,最后将它插入到真实DOM
中便完成了整个真实节点的渲染。
下面是其执行逻辑示意图:
总结
Vue的组件挂载vm.$mount(el)过程如下:
- 将
template
编译为render
函数 - 使用
render
函数生成虚拟节点,函数中需要的变量和方法会去vm
的自身和原型链中查找 - 将虚拟节点创建为真实节点,并递归的插入到页面中
- 使用真实节点替换之前老的节点
到目前为止,我们已经实现了Vue
组件初渲染的整个过程,下面用一张图来总结一下:
源码地址: 传送门
加载全部内容