监听到数据的变化后, 依赖追踪到组件级别的粒度, 组件在内部进行vnode的对比,进行修改, 最后修改挂载修改后的真是DOM
patch可以将vnode渲染成真是的DOM,其实际作用是在现有的真是DOM上进行修改来实现更新视图的,其真实目的不仅是对比vnode之间的差异, 更重要的意义是修改真是DOM。
render函数返回的是vnode, render的参数是createElement, createElement是根据vnode创建真是的DOM。
// vnode.js
export default function (sel, data, children, text, elem) {
const key = data.key
return {
sel,
data,
children,
elem,
text,
key
}
}
********************************************************
// patch.js
import vnode from './vnode'
import createElement from './createElement'
import patchVnode from './patchVnode'
import isSameNode from './isSameNode'
export default function (oldVnode, newVnode) {
// 判断oldVnode是DOM节点还是虚拟节点
if (oldVnode.sel == '' || oldVnode.sel == undefined) {
oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, [], undefined, oldVnode)
}
// 给虚拟节点生成elem指向的真是dom
let newDom = newVnode.elem
if (!newVnode.elem) {
newDom = createElement(newVnode)
}
// 比较新老节点是否是同一个节点
if (isSameNode(oldVnode, newVnode)) {
patchVnode(oldVnode, newVnode)
} else {
console.log('不是同一个节点,进行暴力拆除添加新节点')
if (oldVnode.elem && oldVnode.elem.parentNode && newDom) {
oldVnode.elem.parentNode.insertBefore(newDom, oldVnode.elem)
oldVnode.elem.parentNode.removeChild(oldVnode.elem)
}
}
}
**********************************************************
// createElement.js
function createElement (vnode) {
let domNode = document.createElement(vnode.sel)
// 判断有子节点还是文本
if (vnode.text && (!vnode.children || !vnode.children.length)) {
domNode.innerText = vnode.text
// pivot.parentNode.insertBefore(domNode, pivot)
} else if (vnode.children && vnode.children.length) {
// 递归创建子节点
for (let i=0; i<vnode.children.length; i++) {
let childrenDom = createElement(vnode.children[i])
domNode.append(childrenDom)
}
}
vnode.elem = domNode
return domNode
}
export default createElement
******************************************************
// patchVnode.js
import updateChildren from './updateChildren'
import createElement from './createElement'
export default function (oldVnode, newVnode) {
console.log('是同一个节点, 进行精细化比较')
// 1、判断新旧节点是否是同一个节点
if (oldVnode === newVnode) {
console.log('新旧节点是同一个节点')
return
// 什么都不做
}
// 新节点有text属性
if (newVnode.text && (!newVnode.children || !newVnode.children.length)) {
console.log('新节点是text,没有children')
if (oldVnode.text !== newVnode.text || oldVnode.children) {
oldVnode.elem.innerText = newVnode.text
}
} else {
console.log('新节点有children')
// 新节点没有text属性
// 1、新老节点有childrens属性
if (oldVnode.children && oldVnode.children.length) {
console.log('新老节点都有childrens属性', oldVnode.elem, oldVnode, newVnode)
updateChildren(oldVnode.elem, oldVnode.children, newVnode.children)
} else {
console.log('新节点有children 老节点没有')
oldVnode.elem.innerHTML = ''
// 2、新节点有children 老节点没有
for (let i=0; i<newVnode.children.length; i++) {
let child = newVnode.children[i]
// const newChildDom = createElement(child)
const newChildDom = child.elem
oldVnode.elem.appendChild(newChildDom)
}
}
}
}
*********************************************************
// isSameNode.js
export default function (a, b) {
return a.key === b.key && a.sel === b.sel
}
*********************************************************
// updateChildren.js
import isSameNode from './isSameNode'
import patchVnode from './patchVnode'
/**
* 首先进行节点移动是真是节点的移动, 移动后虚拟节点需要做删除操作,因为下次训话依然比较的是虚拟节点
*/
export default function (parentDom, oldCh, newCh) {
// 旧前
let oldPreIndex = 0;
// 旧后
let oldAftIndex = oldCh.length-1;
// 新前
let newPreIndex = 0;
// 新后
let newAftIndex = newCh.length-1;
// 旧前节点
let oldSNode = oldCh[oldPreIndex];
// 旧后节点
let oldENode = oldCh[oldAftIndex];
// 新前节点
let newSNode = newCh[newPreIndex];
// 新后节点
let newENode = newCh[newAftIndex];
while (oldPreIndex <= oldAftIndex && newPreIndex <= newAftIndex) {
// 新前与旧前
if (!oldSNode || !oldCh[oldPreIndex]) {
oldSNode = oldCh[++oldPreIndex]
} else if (!oldENode || !oldCh[oldAftIndex]) {
oldENode = oldCh[--oldAftIndex]
} else if (!newSNode || !newCh[newPreIndex]) {
newSNode = newCh[++newPreIndex]
} else if (!newENode || !newCh[newAftIndex]) {
newENode = newCh[--newAftIndex]
}else if (isSameNode(newSNode, oldSNode)) {
console.log('①新前与旧前', newSNode.text, oldSNode.text)
patchVnode(newSNode, oldSNode)
oldSNode = oldCh[++oldPreIndex]
newSNode = newCh[++newPreIndex]
// 新前与旧后
} else if (isSameNode(newSNode, oldENode)) {
console.log('②新前与旧后', newSNode.text, oldENode.text)
parentDom.insertBefore(oldENode.elem, oldSNode.elem)
patchVnode(newSNode, oldENode)
oldENode = oldCh[--oldAftIndex]
newSNode = newCh[++newPreIndex]
// 新后与旧前
} else if (isSameNode(newENode, oldSNode)) {
console.log('③新后与旧前', newENode.text, oldSNode.text)
parentDom.insertBefore(oldSNode.elem, oldENode.elem.nextSibling)
patchVnode(newENode, oldSNode)
oldSNode = oldCh[++oldPreIndex]
newENode = newCh[--newAftIndex]
// 新后与旧后
} else if (isSameNode(newENode, oldENode)) {
console.log('④新后与旧后', newENode.text, oldENode.text)
patchVnode(newENode, oldENode)
oldENode = oldCh[--oldAftIndex]
newENode = newCh[--newAftIndex]
} else {
console.log('⑤未匹配到相同的情况下')
let keyMap = {}
// 未匹配到相同的情况下
for (let i=oldPreIndex; i<oldAftIndex; i++){
if (oldCh[i]) {
keyMap[oldCh[i].key] = i
}
}
const target = keyMap[newSNode.key]
// 如果存在则移动
if (target!==undefined) {
console.log('⑤未匹配到相同的情况下-存在-移动', newSNode.text)
patchVnode(oldCh[target], newSNode)
parentDom.insertBefore(oldCh[target].elem, oldSNode.elem)
oldCh[target] = undefined;
} else {
console.log('⑤未匹配到相同的情况下-不存在-添加', newSNode.text)
// 如不存在则新增
parentDom.insertBefore(newSNode.elem, oldSNode.elem)
}
newSNode = newCh[++newPreIndex]
}
}
// 循环结束
// ①如果循环结束,新节点还有剩余直接添加
if (newPreIndex <= newAftIndex) {
console.log('⑥循环完毕-新节点剩余--new--add')
for (let i=newPreIndex; i<=newAftIndex; i++) {
console.log('add...', newCh[i].text)
parentDom.insertBefore(newCh[i].elem, oldSNode.elem)
}
}
// ②如果循环结束,旧节点还有剩余直接删除
if (oldPreIndex <= oldAftIndex) {
console.log('⑥循环完毕-旧节点剩余--old--delete')
for (let i=oldPreIndex; i<=oldAftIndex; i++) {
if (oldCh[i] && oldCh[i].elem) {
console.log('delete...', oldCh[i].text, 'index:', i)
parentDom.removeChild(oldCh[i].elem)
}
}
}
}