vue 的patch函数
目录位置: src\core\vdom\patch.js
1.patch函数从何而来
通过 ==createPatchFunction== 返回一个patch函数,该方法需要传入属性比较、节点操作等方法
伪代码
function createPatchFunction (backend) {
let i, j
const cbs = {}
const { modules, nodeOps } = backend // 获取属性比较、节点操作
// ....内部方法
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// .... patch 过程
}
}
2、对内部方法的部分先解析理解
1、创建空内容的tag虚拟dom节点
function emptyNodeAt (elm) {
return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
}
2、移除节点 对dom某些节点进行移除
function removeNode (el) {
const parent = nodeOps.parentNode(el)
// element may have already been removed due to v-html / v-text
if (isDef(parent)) {
nodeOps.removeChild(parent, el)
}
}
3、isUnknownElement 是否是未知置标签
// 全局有个config 配置忽略tag
config.ignoredElements
4、sameVnode 比较两个vnode,判断是否需要进行patchVnode
最基本需要满足 key值、tag值相同,或者是个异步组件
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
5、createKeyToOldIdx(children, beginIdx, endIdx) 数组创建下标映射
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
使用demo
const list = [{key: a}, {key:b}]
const map = createKeyToOldIdx(list, 0, 1)
// map = {a: 0, b: 1}
3、patch函数源码解析
function patch (oldVnode, vnode, hydrating, removeOnly) {
//新节点vnode为空,代表销毁改节点,调用destroy钩子 区别于生命周期的钩子
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
// 该数组其实是收集组件节点的,用来维护一个插入队列。
const insertedVnodeQueue = []
if (isUndef(oldVnode)) { //oldVnode不存在 = null或者undefined
// empty mount (likely as component), create new root element
// 分支1、初始化走这里
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType) // 是真实节点不为null或者undefined
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node // 都存在vnode
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
// 这里之下都是处理初始化的
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
// ssr的渲染处理
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
}
// ------------------------ 这两步不知道干嘛的
// either not server-rendered, or hydration failed.
// create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode) // dom树转vnnode节点 细节先蔽先
}
// replacing existing element
const oldElm = oldVnode.elm // 获得dom树
const parentElm = nodeOps.parentNode(oldElm) // 获得dom节点
// create new node 创建新vnode
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition + _leaveCb 目前估计是
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm, // 父节点
nodeOps.nextSibling(oldElm) //追加的地方
)
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node 删除旧节点
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}