// 渲染虚拟DOM
// 虚拟DOM节点树
// 承载DOM节点的容器,父元素
function render(vnode,container) {
// 首次渲染
mount(vnode,container);
};
// 渲染虚拟DOM
// 更新函数
// 旧的虚拟DOM节点
// 新的DOM节点
// 承载DOM节点的容器
function patch(oldVNode, newVNode, container) {
// 新节点的VNode类型
let newVNodeFlag = newVNode.flag;
// 旧节点的VNode类型
let oldVNodeFlag = oldVNode.flag;
// 如果新节点与旧节点的类型不一致
// 如果不一致的情况下,相当于其节点发生了变化
// 直接进行替换操作即可
// 这里判断的是如果一个是 TEXT 一个是 Element
// 类型判断
if (newVNodeFlag !== oldVNodeFlag) {
replaceVNode(oldVNode, newVNode, container);
}
// 由于在新建时创建Element和Text的时候使用的是两个函数进行操作的
// 在更新的时候也是同理的
// 也应该针对不同的修改进行不同的操作
// 如果新节点与旧节点的HTML相同
else if (newVNodeFlag == vnodeTypes.HTML) {
// 替换元素操作
patchMethos.patchElement(oldVNode, newVNode, container);
}
// 如果新节点与旧节点的TEXT相同
else if (newVNodeFlag == vnodeTypes.TEXT) {
// 替换文本操作
patchMethos.patchText(oldVNode, newVNode, container);
}
}
// 更新VNode方法集
const patchMethos = {
// 替换文本操作
// 旧的虚拟DOM节点
// 新的DOM节点
// 承载DOM节点的容器
patchText(oldVNode,newVNode,container){
// 获取到el,并将 oldVNode 赋值给 newVNode
let el = (newVNode.el = oldVNode.el);
// 如果 newVNode.children 不等于 oldVNode.children
// 其他情况就是相等则没有任何操作,不需要更新
if(newVNode.children !== oldVNode.children){
// 直接进行替换操作
el.nodeValue = newVNode.children;
}
}
};
// 替换虚拟DOM
function replaceVNode(oldVNode, newVNode, container) {
// 在原有节点中删除旧节点
container.removeChild(oldVNode.el);
// 重新渲染新节点
mount(newVNode, container);
}
上述方法简单的实现了对Text更新的一个替换操作,由于Text替换操作比较简单,所以这里就先实现,仅仅完成了对Text的更新是远远不够的,当Element进行操作的时也是需要更新的。相对来说Text的更新要比Element更新要简单很多的,Element更新比较复杂所以放到了后面,因为比较重要嘛,哈哈~
首先想要进行Element替换之前要确定哪些Data数据进行了变更,然后才能对其进行替换操作,这样的话需要确定要更改的数据,然后替换掉原有数据,才能进行下一步更新操作。
// 更新VNode方法集
const patchMethos = {
// 替换元素操作
// 旧的虚拟DOM节点
// 新的DOM节点
// 承载DOM节点的容器
patchElement(oldVNode,newVNode,container){
// 如果 newVNode 的标签名称与 oldVNode 标签名称不一样
// 既然标签都不一样则直接替换就好了,不需要再进行其他多余的操作
if(newVNode.tag !== oldVNode.tag){
replaceVNode(oldVNode,newVNode,container);
return;
}
// 更新el
let el = (newVNode.el = oldVNode.el);
// 获取旧的Data数据
let oldData = oldVNode.data;
// 获取新的Data数据
let newData = newVNode.data;
// 如果新的Data数据存在
// 进行更新和新增
if(newData){
for(let attr in newData){
let oldVal = oldData[attr];
let newVal = newData[attr];
domAttributeMethod.patchData(el,attr,oldVal,newVal);
}
}
// 如果旧的Data存在
// 检测更新
if(oldData){
for(let attr in oldData){
let oldVal = oldData[attr];
let newVal = newData[attr];
// 如果旧数据存在,新数据中不存在
// 则表示已删除,需要进行更新操作
if(oldVal && !newVal.hasOwnProperty(attr)){
// 既然新数据中不存在,则新数据则传入Null
domAttributeMethod.patchData(el,attr,oldVal,null);
}
}
}
}
};
// dom添加属性方法
const domAttributeMethod = {
// 修改Data数据方法
patchData (el,key,prv,next){
switch(key){
case "style":
this.setStyle(el,key,prv,next);
// 添加了这里,看我看我 (●'◡'●)
// 添加遍历循环
// 循环旧的data
this.setOldVal(el,key,prv,next);
break;
case "class":
this.setClass(el,key,prv,next);
break;
default :
this.defaultAttr(el,key,prv,next);
break;
}
},
// 遍历旧数据
setOldVal(el,key,prv,next){
// 遍历旧数据
for(let attr in prv){
// 如果旧数据存在,新数据中不存在
if(!next.hasOwnProperty(attr)){
// 直接赋值为字符串
el.style[attr] = "";
}
}
},
// 修改事件注册方法
addEvent(el,key,prev,next){
// 添加了这里,看我看我 (●'◡'●)
// prev 存在删除原有事件,重新绑定新的事件
if(prev){
el.removeEventListener(key.slice(1),prev);
}
if(next){
el.addEventListener(key.slice(1),next);
}
}
}
// 更新VNode方法集
const patchMethos = {
// 替换元素操作
// 旧的虚拟DOM节点
// 新的DOM节点
// 承载DOM节点的容器
patchElement(oldVNode,newVNode,container){
// 如果 newVNode 的标签名称与 oldVNode 标签名称不一样
// 既然标签都不一样则直接替换就好了,不需要再进行其他多余的操作
if(newVNode.tag !== oldVNode.tag){
replaceVNode(oldVNode,newVNode,container);
return;
}
// 更新el
let el = (newVNode.el = oldVNode.el);
// 获取旧的Data数据
let oldData = oldVNode.data;
// 获取新的Data数据
let newData = newVNode.data;
// 如果新的Data数据存在
// 进行更新和新增
if(newData){
for(let attr in newData){
let oldVal = oldData[attr];
let newVal = newData[attr];
domAttributeMethod.patchData(el,attr,oldVal,newVal);
}
}
// 如果旧的Data存在
// 检测更新
if(oldData){
for(let attr in oldData){
let oldVal = oldData[attr];
let newVal = newData[attr];
// 如果旧数据存在,新数据中不存在
// 则表示已删除,需要进行更新操作
if(oldVal && !newVal.hasOwnProperty(attr)){
// 既然新数据中不存在,则新数据则传入Null
domAttributeMethod.patchData(el,attr,oldVal,null);
}
}
}
// 添加了这里
// 更新子元素
// 旧子元素类型
// 新子元素类型
// 旧子元素的children
// 新子元素的children
// el元素,容器
this.patchChildren(
oldVNode.childrenFlag,
newVNode.childrenFlag,
oldVNode.children,
newVNode.children,
el,
);
},
// 更新子元素
// 旧子元素类型
// 新子元素类型
// 旧子元素的children
// 新子元素的children
// el元素,容器
patchChildren(...arg){
let [oldChildrenFlag,newChildrenFlag,oldChildren,newChildren,container] = arg;
switch(oldChildrenFlag){
// 如果旧元素的子元素为一个
case childTeyps.SINGLE:
this.upChildSingle(...arg);
break;
// 如果旧元素的子元素为空
case childTeyps.EMPTY:
this.upChildEmpty(...arg);
break;
// 如果旧元素的子元素为多个
case childTeyps.MULTIPLE:
this.upChildMultiple(...arg);
break;
}
},
upChildSingle(...arg){
let [oldChildrenFlag,newChildrenFlag,oldChildren,newChildren,container] = arg;
// 循环新的子元素
switch(newChildrenFlag){
// 如果新元素的子元素为一个
case childTeyps.SINGLE:
patch(oldChildren,newChildren,container);
break;
// 如果新元素的子元素为空
case childTeyps.EMPTY:
container.removeChild(oldChildren.el);
break;
// 如果新元素的子元素多个
case childTeyps.MULTIPLE:
container.removeChild(oldChildren.el);
for(let i = 0;i<newChildren.length;i++){
mount(newChildren,container);
}
break;
}
},
upChildEmpty(...arg){
let [oldChildrenFlag,newChildrenFlag,oldChildren,newChildren,container] = arg;
// 循环新的子元素
switch(newChildrenFlag){
// 如果新元素的子元素为一个
case childTeyps.SINGLE:
mount(newChildren,container);
break;
// 如果新元素的子元素为空
case childTeyps.EMPTY:
break;
// 如果新元素的子元素多个
case childTeyps.MULTIPLE:
container.removeChild(oldChildren.el);
for(let i = 0;i<newChildren.length;i++){
mount(newChildren,container);
}
break;
}
},
upChildMultiple(...arg){
let [oldChildrenFlag,newChildrenFlag,oldChildren,newChildren,container] = arg;
// 循环新的子元素
switch(newChildrenFlag){
// 如果新元素的子元素为一个
case childTeyps.SINGLE:
for(let i = 0;i<oldChildren.length;i++){
container.removeChild(oldChildren.el);
}
mount(newChildren,container);
break;
// 如果新元素的子元素为空
case childTeyps.EMPTY:
for(let i = 0;i<oldChildren.length;i++){
container.removeChild(oldChildren.el);
}
break;
// 如果新元素的子元素多个
case childTeyps.MULTIPLE:
// **
// 暂时搁置 这里是所有节点的对比
// **
break;
}
}
};
// 更新VNode方法集
// 添加 oldMoreAndNewMore 方法
const patchMethos = {
upChildMultiple(...arg) {
let [oldChildrenFlag, newChildrenFlag, oldChildren, newChildren, container] = arg;
// 循环新的子元素
switch (newChildrenFlag) {
// 如果新元素的子元素为一个
case childTeyps.SINGLE:
for (let i = 0; i < oldChildren.length; i++) {
// 遍历删除旧元素
container.removeChild(oldChildren.el);
}
// 添加新元素
mount(newChildren, container);
break;
// 如果新元素的子元素为空
case childTeyps.EMPTY:
for (let i = 0; i < oldChildren.length; i++) {
// 删除所有子元素
container.removeChild(oldChildren.el);
}
break;
// 如果新元素的子元素多个
case childTeyps.MULTIPLE:
// 修改了这里 (●'◡'●)
this.oldMoreAndNewMore(...arg);
break;
},
oldMoreAndNewMore(...arg) {
let [oldChildrenFlag, newChildrenFlag, oldChildren, newChildren, container] = arg;
let lastIndex = 0;
for (let i = 0; i < newChildren.length; i++) {
let newVnode = newChildren;
let j = 0;
// 新的元素是否找到
let find = false;
for (; j < oldChildren.length; j++) {
let oldVnode = oldChildren[j];
// key相同为同一个元素
if (oldVnode.key === newVnode.key) {
find = true;
patch(oldVnode, newVnode, container);
if (j < lastIndex) {
if(newChildren[i-1].el){
// 需要移动
let flagNode = newChildren[i-1].el.nextSibling;
container.insertBefore(oldVnode.el, flagNode);
}
break;
}
else {
lastIndex = j;
}
}
}
// 如果没有找到旧元素,需要新增
if (!find) {
// 需要插入的标志元素
let flagNode = i === 0 ? oldChildren[0].el : newChildren[i-1].el;
mount(newVnode, container, flagNode);
}
// 移除元素
for (let i = 0; i < oldChildren.length; i++) {
// 旧节点
const oldVNode = oldChildren;
// 新节点key是否在旧节点中存在
const has = newChildren.find(next => next.key === oldVNode.key);
if (!has) {
// 如果不存在删除
container.removeChild(oldVNode.el)
}
}
}
}
};
// 修改mount函数
// flagNode 标志node 新元素需要插入到哪里
function mount(vnode, container, flagNode) {
// 所需渲染标签类型
let { flag } = vnode;
// 如果是节点
if (flag === vnodeTypes.HTML) {
// 调用创建节点方法
mountMethod.mountElement(vnode, container, flagNode);
} // 如果是文本
else if (flag === vnodeTypes.TEXT) {
// 调用创建文本方法
mountMethod.mountText(vnode, container);
};
};
// 修改mountElement
const mountMethod = {
// 创建HTML元素方法
// 修改了这里 (●'◡'●) 添加 flagNode 参数
mountElement(vnode, container, flagNode) {
// 属性,标签名,子元素,子元素类型
let { data, tag, children, childrenFlag } = vnode;
// 创建的真实节点
let dom = document.createElement(tag);
// 添加属性
data && domAttributeMethod.addData(dom, data);
// 在VNode中保存真实DOM节点
vnode.el = dom;
// 如果不为空,表示有子元素存在
if (childrenFlag !== childTeyps.EMPTY) {
// 如果为单个元素
if (childrenFlag === childTeyps.SINGLE) {
// 把子元素传入,并把当前创建的DOM节点以父元素传入
// 其实就是要把children挂载到 当前创建的元素中
mount(children, dom);
} // 如果为多个元素
else if (childrenFlag === childTeyps.MULTIPLE) {
// 循环子节点,并创建
children.forEach((el) => mount(el, dom));
};
};
// 添加元素节点 修改了这里 (●'◡'●)
flagNode ? container.insertBefore(dom, flagNode) : container.appendChild(dom);
}
}
最终使用:
const VNODEData = [
"div",
{id:"test",key:789},
[
createElement("p",{
key:1,
style:{
color:"red",
background:"pink"
}
},"节点一"),
createElement("p",{
key:2,
"@click":() => console.log("click me!!!")
},"节点二"),
createElement("p",{
key:3,
class:"active"
},"节点三"),
createElement("p",{key:4},"节点四"),
createElement("p",{key:5},"节点五")
]
];
let VNODE = createElement(...VNODEData);
render(VNODE,document.getElementById("app"));
const VNODEData1 = [
"div",
{id:"test",key:789},
[
createElement("p",{
key:6
},"节点六"),
createElement("p",{
key:1,
style:{
color:"red",
background:"pink"
}
},"节点一"),
createElement("p",{
key:5
},"节点五"),
createElement("p",{
key:2
},"节点二"),
createElement("p",{
key:4
},"节点四"),
createElement("p",{
key:3,
class:"active"
},"节点三")
]
];
setTimeout(() => {
let VNODE = createElement(...VNODEData1);
render(VNODE,document.getElementById("app"));
},1000)
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) | 黑马程序员IT技术论坛 X3.2 |