虚拟DOM渲染过程: template
=> render function渲染函数
=> vnode
=> 真实DOM
=> 视图
vnode是啥❓就是一个普通的javascript对象,或者说是不同类型的DOM元素,简单的说 vnode就是可以理解成节点描述对象
具体实现看代码🔽
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="./renderer.js"></script>
<script>
/**
* 1.通过h函数创建一个vnode
* */
const vnode = h('div', {class: 'why', id: 'aaa'}, [
h('h2', null, '当前计数:100'),
h('button', null, '+1')
])
console.log(vnode);
//2.通过mount函数,将vnode挂载到div #app上
mount(vnode, document.querySelector('#app'))
//3.创建一个新的vnode
const vnode1 = h('h2', {class: 'style', id: 'aaa'}, '哈哈哈')
//两个vnode做一个diff算法
setTimeout(() => {
patch(vnode, vnode1)
}, 2000)
</script>
</body>
</html>
- renderer.js
const h = (tag, props, children) => {
//vnode -> javascript对象{}
return {
tag, //节点名称
props, //文本节点(事件)
children //子节点
}
}
const mount = (vnode, container) => {
//1.创建出真实的原生,并且在vnode上保留el
const el = vnode.el = document.createElement(vnode.tag)
//2.处理props(将虚拟节点的props放到el中)
if(vnode.props) {
for(const key in vnode.props) {
const value = vnode.props[key]
if(key.startsWith('on')) { //对事件监听的判断
el.addEventListener(key.slice(2).toLowerCase(), value)
} else {
el.setAttribute(key, value); //给el设置属性
}
}
}
//3.处理children
if(vnode.children) {
if(typeof vnode.children === "string") {
el.textContent = vnode.children;
} else {
vnode.children.forEach(item => {
mount(item, el) //递归调用
})
}
}
//4.将el挂载到container中
container.appendChild(el);
}
const patch = (n1, n2) => {
if(n1.tag !== n2.tag) {
const n1ElPrent = n1.el.parentElement;
n1ElPrent.removeChild(n1.el);
mount(n2, n1ElPrent)
} else {
//1.取出element对象,并且在n2中进行保存
const el = n2.el = n1.el;
//2.处理props
const oldProps = n1.props || {}
const newProps = n2.props || {}
//2.1.获取所有的newProps添加到el
for(const key in newProps) {
const oldValue = oldProps[key]
const newValue = newProps[key]
if(newValue !== oldValue) {
if(key.startsWith('on')) { //对事件监听的判断
el.addEventListener(key.slice(2).toLowerCase(), newValue)
} else {
el.setAttribute(key, newValue); //给el设置属性
}
}
}
//2.2.删除旧的props
for(const key in oldProps) {
if(!(key in newProps)) {
if(key.startsWith('on')) { //对事件监听的判断
const value = oldProps[key]
el.removeEventListener(key.slice(2).toLowerCase(), value)
} else {
el.removeAttribute(key); //给el设置属性
}
}
}
//3.处理children
const oldChildren = n1.children || []
const newChildren = n2.children || []
if(typeof newChildren === "string") {
//边界情况
if(typeof oldChildren === 'string') {
if(newChildren !== oldChildren) {
el.textContent = newChildren
}
} else {
el.innerHTML = newChildren
}
} else {
if(typeof oldChildren === 'string') {
el.innerHTML = ''
newChildren.forEach(item => {
mount(item, el)
})
} else {
//oldChilren和newChildren进行Diff算法
//前面有相同节点的元素进行patch操作
const commonLength = Math.min(oldChildren.length, newChildren.length) //比较长度 拿到更短的长度
for (let i = 0; i < commonLength; i++) {
patch(oldChildren[i], newChildren[i])
}
//newchildren更长 需要将多余的节点挂载
if(newChildren.length > oldChildren.length) {
//切割遍历
newChildren.slice(oldChildren.length).forEach(item => {
mount(item, el)
})
}
//newchildren的length小于oldChildren
if(newChildren.length < oldChildren.length) {
oldChildren.slice(newChildren.length).forEach(item => {
//移除多余的子节点
el.removeChild(item.el)
})
}
}
}
}
}