Vue手写实现组件初渲染
作者:夏日
前言
在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
组件初渲染的整个过程,下面用一张图来总结一下:
到此这篇关于Vue手写实现组件初渲染的文章就介绍到这了,更多相关Vue组件初渲染内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
源码地址: 传送门