这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战
初次渲染时通过调用_render
函数创建虚拟DOM返回给_update
函数,在_update
函数内部主要是通过patch
函数进行虚拟DOM至真实DOM的创建并替换$el
渲染至浏览器中
初次渲染时调用patch
函数,会将当前的真实DOM和通过_render
函数生成的虚拟DOM作为参数传入
patch(vm.$el, vnode);
复制代码
初始化patch
函数
export function patch(oldVnode, vnode) {
}
复制代码
oldVnode:旧的虚拟节点,可能是真实DOM,也可能是上次渲染使用的虚拟DOM,若是真实DOM,则存在nodeType
属性,否则无
vnode:新的虚拟DOM,需要渲染至浏览器展示的最新数据
在patch
函数中,可以根据oldVnode.nodeType
来判断是初次渲染操作还是数据变换导致更新操作,接下来的重点便是实现初次渲染流程
export function patch(oldVnode, vnode) {
/**
* 判断是更新还是初次渲染
*/
const isRealElement = oldVnode.nodeType;
// 是真实节点
if (isRealElement) {
// 初次渲染操作
}else {
// 更新操作
}
}
复制代码
现在有如下HTML
<div id="app">
<p>{{name}}</p>
</div>
复制代码
实例化Vue
const vm = new Vue({
el: '#app',
data() {
return {
name: 'nordon',
}
},
})
复制代码
在初次渲染时oldVnode
是真实DOM节点#app
,那么我们是可以获取到当前元素以及其父节点document.body
const oldEle = oldVnode;
const parentEle = oldEle.parentNode;
复制代码
通过虚拟DOM创建的新的真实DOM如下(具体创建细节下文实现)
<div id="app">
<p>nordon</p>
</div>
复制代码
将新的真实DOM替换掉老的真实DOM,这个过程通过父节点进行操作,将新的真实DOM插入到老的真实DOM后面,然后再将老的真实DOM删除掉,此时页面上便是需要展示的内容
parentEle.insertBefore(el, oldEle.nextSibling);
parentEle.removeChild(oldEle);
复制代码
Tips:在真实的DOM操作中,是没有办法直接将自己删除掉的,都是利用父节点进行删除
整个渲染过程核心点便是如何通过递归创建真实DOM节点,函数如下
/**
* 根据虚拟节点 创建真实DOM
*/
function createEle(vnode) {
const { tag, children, key, data, text } = vnode;
// 是标签
if (typeof tag === "string") {
vnode.el = document.createElement(tag)
updateProperties(vnode) // 增加属性
children.forEach(child => {
// 递归创建儿子节点, 将儿子节点放到父节点中
return vnode.el.appendChild(createEle(child))
})
} else {
// 是文本
// 虚拟dom上映射着真实dom, 方便后续更新操作
vnode.el = document.createTextNode(text)
}
return vnode.el
}
复制代码
需要注意在创建真实DOM过程中,需要对属性进行处理
/**
* 属性处理
*/
function updateProperties(vnode) {
let newProps = vnode.data;
let el = vnode.el
// 遍历属性
for (const key in newProps) {
if(key === 'style') { /// 样式进行处理
for (const styleName in newProps.style) {
el.style[styleName] = newProps.style[styleName]
}
}else if(key === 'class'){ // 样式进行处理
el.className = newProps[key]
} else { // 普通属性处理
el.setAttribute(key, newProps[key])
}
}
}
复制代码
至此整个patch
流程如下
export function patch(oldVnode, vnode) {
/**
* 判断是更新还是初次渲染
*/
const isRealElement = oldVnode.nodeType;
// 是真实节点
if (isRealElement) {
// 初次渲染操作
const oldEle = oldVnode; // div#app
const parentEle = oldEle.parentNode; // document.body
let el = createEle(vnode);
parentEle.insertBefore(el, oldEle.nextSibling);
parentEle.removeChild(oldEle);
// 需要将渲染好的结果 返回
return el
}else {
// 更新操作
}
}
复制代码