@typex-core_view_vdom_patch.js
/*
* @Author: caiwu
* @Description:
* @CreateDate:
* @LastEditor:
* @LastEditTime: 2022-11-22 16:20:40
*/
import { default as h } from './createVnode'
import pluginContext from '../../pluginContext'
import {
getVdomOrElm,
getVnodeOrIns,
getVdomOrIns,
setVdomOrElm,
setVnodeOrIns,
setVdomOrIns,
} from '../../mappings'
import { isUndef, isDef } from '../../utils'
import { vnodeType } from '../../constDefine'
const { VFUNCTION, VCOMPONENT, VTEXT } = vnodeType
/**
* @description 判断是否相同节点
* @param {*} vnode
* @param {*} oldVnode
* @returns {*}
*/
function sameVnode (vnode, oldVnode) {
return vnode?.key === oldVnode?.key && vnode?.tag === oldVnode?.tag
}
/**
* @description 在老节点中查找id
* @param {*} node
* @param {*} oldCh
* @param {*} start
* @param {*} end
* @returns {*}
*/
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
/**
* @description 生成id映射
* @param {*} children
* @param {*} beginIdx
* @param {*} endIdx
* @returns {*}
*/
function createKeyToOldIdx (children, beginIdx, endIdx) {
const map = {}
for (let i = beginIdx; i <= endIdx; ++i) {
const key = children[i]?.key
if (isDef(key)) {
map[key] = i
}
}
return map
}
/**
* @description 增加节点
* @param {*} parentElm
* @param {*} [before=null]
* @param {*} vnodes
* @param {*} startIdx
* @param {*} endIdx
*/
function addVnodes (parentElm, before = null, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx]
if (ch != null) {
const elm = pluginContext.platform.createElm(ch)
pluginContext.platform.insertBefore(parentElm, elm, before)
}
}
}
/**
* @description 节点删除
* @param {*} parentElm
* @param {*} oldCh
* @param {*} startIdx
* @param {*} endIdx
*/
function removeVnodes (parentElm, oldCh, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
const vnode = oldCh[startIdx]
if (vnode != null) {
let dom = getElmByVnode(vnode)
dom && pluginContext.platform.removeChild(parentElm, dom)
}
}
}
/**
* @description children更新函数
* @param {*} parentElm
* @param {*} newCh
* @param {*} oldCh
*/
function updateChildren (parentElm, newCh, oldCh) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let newEndIdx = newCh.length - 1
let oldStartVnode = oldCh[0]
let newStartVnode = newCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx
let idxInOld
let elmToMove
let before
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVnode === null) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode might have been moved left
} else if (oldEndVnode === null) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (newStartVnode === null) {
newStartVnode = newCh[++newStartIdx]
} else if (newEndVnode === null) {
newEndVnode = newCh[--newEndIdx]
// 新头=旧头
} else if (sameVnode(newStartVnode, oldStartVnode)) {
patchVnode(newStartVnode, oldStartVnode)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
// 新尾=旧尾
} else if (sameVnode(newEndVnode, oldEndVnode)) {
patchVnode(newEndVnode, oldEndVnode)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
// 旧头=新尾
} else if (sameVnode(newEndVnode, oldStartVnode)) {
// Vnode moved right
patchVnode(newEndVnode, oldStartVnode)
pluginContext.platform.insertBefore(
parentElm,
getElmByVnode(oldStartVnode),
getElmByVnode(oldEndVnode).nextSibling
)
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
// 新头=旧尾
} else if (sameVnode(newStartVnode, oldEndVnode)) {
// Vnode moved left
patchVnode(newStartVnode, oldEndVnode)
pluginContext.platform.insertBefore(
parentElm,
getElmByVnode(oldEndVnode),
getElmByVnode(oldStartVnode)
)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
}
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) {
// New element
pluginContext.platform.insertBefore(
parentElm,
pluginContext.platform.createElm(newStartVnode),
getElmByVnode(oldStartVnode)
)
} else {
elmToMove = oldCh[idxInOld]
if (elmToMove.tag !== newStartVnode.tag) {
pluginContext.platform.insertBefore(
parentElm,
pluginContext.platform.createElm(newStartVnode),
getElmByVnode(oldStartVnode)
)
} else {
patchVnode(newStartVnode, elmToMove)
oldCh[idxInOld] = null
pluginContext.platform.insertBefore(
parentElm,
getElmByVnode(elmToMove),
getElmByVnode(oldStartVnode)
)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (newStartIdx <= newEndIdx) {
before = isUndef(newCh[newEndIdx + 1]) ? null : getVdomOrElm(newCh[newEndIdx + 1])
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
}
if (oldStartIdx <= oldEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
function getVdomByVnode (vnode) {
switch (vnode.vnodeType) {
case VFUNCTION:
return getVdomOrIns(vnode)
case VCOMPONENT:
return getVdomOrIns(getVnodeOrIns(vnode))
default:
return vnode
}
}
function getElmByVnode (vnode) {
return getVdomOrElm(getVdomByVnode(vnode))
}
/**
* @description 节点比对
* @param {*} vnode
* @param {*} oldVnode
*/
function patchVnode (vnode, oldVnode) {
if (oldVnode === vnode) return
if (vnode.vnodeType === VFUNCTION) {
// 函数组件
const oldVdom = getVdomByVnode(oldVnode)
const newVdom = vnode.tag(h, vnode.props)
return patch(newVdom, oldVdom)
} else if (vnode.vnodeType === VCOMPONENT) {
// 常规组件
const oldVdom = getVdomByVnode(oldVnode)
const ins = getVnodeOrIns(oldVnode)
setVnodeOrIns(vnode, ins)
ins.props = Object.freeze({ ...vnode.props })
const newVdom = ins._generateVdom_(h)
setVdomOrIns(ins, newVdom)
setVnodeOrIns(ins, vnode)
return patch(newVdom, oldVdom)
} else if (vnode.vnodeType === VTEXT) {
const elm = getVdomOrElm(oldVnode)
setVdomOrElm(elm, vnode)
pluginContext.platform.updateProps(vnode, oldVnode)
return elm
} else {
// 重新映射elm和vn
const elm = getVdomOrElm(oldVnode)
setVdomOrElm(elm, vnode)
const oldCh = oldVnode.children
const ch = vnode.children
pluginContext.platform.updateProps(vnode, oldVnode)
if (oldCh !== ch) updateChildren(elm, ch, oldCh)
return elm
}
}
/**
* @description diff函数
* @export
* @param {*} vnode
* @param {*} oldVnode
* @returns {*}
*/
export default function patch (vnode, oldVnode) {
if (oldVnode === vnode) return
if (!vnode) {
const ins = getVnodeOrIns(oldVnode)
const oldVdom = getVdomOrIns(ins)
const newVdom = ins._generateVdom_(h)
setVdomOrIns(ins, newVdom)
patchVnode(newVdom, oldVdom)
return getVdomOrElm(newVdom)
}
// 没有oldvnode 直接创建新dom
if (isUndef(oldVnode)) {
const elm = pluginContext.platform.createElm(vnode)
return elm
}
const isRealElment = isDef(oldVnode.nodeType)
// oldvnode 是dom,先转化为虚拟节点
if (isRealElment) {
const elm = oldVnode
oldVnode = pluginContext.platform.domToVNode(oldVnode)
setVdomOrElm(elm, oldVnode)
}
// 相同节点则执行更新逻辑
if (sameVnode(vnode, oldVnode)) {
patchVnode(vnode, oldVnode)
} else {
let oldElm = getElmByVnode(oldVnode)
const newElm = pluginContext.platform.createElm(vnode)
pluginContext.platform.replaceChild(oldElm.parentNode, newElm, oldElm)
}
return getElmByVnode(vnode)
}