Vue 源码
你想要的 vue 源码分析 · 21
响应式数据发生变化,触发渲染 watcher 的回调函数,进而执行组件的更新过程 组件更新过程 组件更新过程是新旧 vnode diff 的过程 新旧节点不相同:1. 创建新节点 2.更新父占位符节点 3.删除旧节点 新旧节点相同:1. 获取他们 children,根据不同情况做不同的更新逻辑 最复杂是新旧节点相同,而且都存在子节点,会执行 updateChildren diff 算法 源码 组件…
响应式数据发生变化,触发渲染 watcher 的回调函数,进而执行组件的更新过程 组件更新过程 组件更新过程是新旧 vnode diff 的过程 新旧节点不相同:1. 创建新节点 2.更新父占位符节点 3.删除旧节点 新旧节点相同:1. 获取他们 children,根据不同情况做不同的更新逻辑 最复杂是新旧节点相同,而且都存在子节点,会执行 updateChildren diff 算法 源码 组件…
响应式数据发生变化,触发渲染 watcher 的回调函数,进而执行组件的更新过程
// src/core/instance/lifecycle.js
export function mountComponent(vm: Component, el: ?Element, hydrating?: boolean): Component {
updateComponent = () => {
vm._update(vm._render(), hydrating);
};
// 渲染watcher
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate');
}
}
},
true /* isRenderWatcher */
);
}
// src/core/instance/lifecycle.js
Vue.prototype._update = function(vnode: VNode, hydrating?: boolean) {
const vm: Component = this;
// 组件更新再次执行_update prevVnode是有的
const prevVnode = vm._vnode;
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
}
// ...
};
// src/core/vdom/patch.js
return function patch(oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode);
return;
}
let isInitialPatch = false;
const insertedVnodeQueue = [];
// 因为oldVnode已经定义,所以会走到sameVnode来判断是否是相同的vnode,进而走不同的更新逻辑
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true;
createElm(vnode, insertedVnodeQueue);
} else {
// 组件更新时isRealElement原生节点为false
const isRealElement = isDef(oldVnode.nodeType);
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// 比对新旧vnode,sameVnode是否相同
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
} else {
if (isRealElement) {
// ...
}
// replacing existing element
const oldElm = oldVnode.elm;
const parentElm = nodeOps.parentNode(oldElm);
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// 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(parentElm, [oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm;
};
sameVNode(oldVnode, vnode) 判断它们是否是相同的 VNode
// src/core/vdom/patch.js
function sameVnode(a, b) {
// 如果两个vnode的key不同,就代表是不同的组件,key就是v-fof是key
// 如果是同一个组件,并且还是同步组件, 则判断isComment、data,input类型是否相同
// 如果是同一个组件,并且是异步组件,则判断asyncFactory是否相同
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)));
}
// 以当前旧节点为参考节点,创建新节点,插入到DOM中
const oldElm = oldVnode.elm;
const parentElm = nodeOps.parentNode(oldElm);
// 创建新节点
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
);
if (isDef(vnode.parent)) {
// 在render过程中,通过render函数生成的渲染vnode 指向父的vnode
let ancestor = vnode.parent;
// 找到当前vnode的父占位符节点
// isPatchable是一直遍历组件vnode,找到真实的vnode(根vnode)
const patchable = isPatchable(vnode);
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
// 先执行各个module的destroy钩子函数
cbs.destroy[i](ancestor);
}
ancestor.elm = vnode.elm;
// 如果占位符是一个可挂载的节点
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
// 则执行module的create钩子函数
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;
}
}
// 把oldVnode从当前DOM树中删除,如果父节点存在,则执行removeVnodes方法
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
// 遍历待删除的vnodes
function removeVnodes(parentElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx];
if (isDef(ch)) {
if (isDef(ch.tag)) {
// 从DOM中移除节点,并执行module的remove钩子函数,并对子节点递归调用removeAndInvokeRemoveHook函数
removeAndInvokeRemoveHook(ch);
// 执行module的destroy钩子函数,以及vnode的destory钩子函数,并对子vnode递归调用invokeDestroyHook
// 在执行invokeDestroyHook会执行vnode的destory生命周期
invokeDestroyHook(ch);
} else {
// 调用平台API把真正的DOM节点移除
removeNode(ch.elm);
}
}
}
}
// 从DOM中移除节点,并执行module的remove钩子函数,并对子节点递归调用removeAndInvokeRemoveHook函数
function removeAndInvokeRemoveHook(vnode, rm) {
if (isDef(rm) || isDef(vnode.data)) {
let i;
const listeners = cbs.remove.length + 1;
if (isDef(rm)) {
// we have a recursively passed down rm callback
// increase the listeners count
rm.listeners += listeners;
} else {
// directly removing
rm = createRmCb(vnode.elm, listeners);
}
// recursively invoke hooks on child component root node
if (isDef((i = vnode.componentInstance)) && isDef((i = i._vnode)) && isDef(i.data)) {
removeAndInvokeRemoveHook(i, rm);
}
for (i = 0; i < cbs.remove.length; ++i) {
cbs.remove[i](vnode, rm);
}
if (isDef((i = vnode.data.hook)) && isDef((i = i.remove))) {
i(vnode, rm);
} else {
rm();
}
} else {
removeNode(vnode.elm);
}
}
// 执行module的destroy钩子函数,以及vnode的destory钩子函数,并对子vnode递归调用invokeDestroyHook
function invokeDestroyHook(vnode) {
let i, j;
const data = vnode.data;
if (isDef(data)) {
if (isDef((i = data.hook)) && isDef((i = i.destroy))) i(vnode);
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode);
}
if (isDef((i = vnode.children))) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j]);
}
}
}
// src/core/vdom/patch.js
// patchVnode就是把新的vnode patch到旧的vnode
function patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) {
if (oldVnode === vnode) {
return;
}
const elm = (vnode.elm = oldVnode.elm);
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue);
} else {
vnode.isAsyncPlaceholder = true;
}
return;
}
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
// reset by the hot-reload-api and we need to do a proper re-render.
if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {
vnode.componentInstance = oldVnode.componentInstance;
return;
}
// 核心步骤1:执行prepatch钩子函数
// prepatch是拿到新的vnode组件配置以及组件实例,去执行updateChildComponent方法,对子components做更新
let i;
const data = vnode.data;
if (isDef(data) && isDef((i = data.hook)) && isDef((i = i.prepatch))) {
i(oldVnode, vnode);
}
// 核心步骤2:执行update钩子函数
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
if (isDef((i = data.hook)) && isDef((i = i.update))) i(oldVnode, vnode);
}
// 核心步骤3:完成patch过程
const oldCh = oldVnode.children;
const ch = vnode.children;
if (isUndef(vnode.text)) {
// 如果1. 新老子节点都存在且不相同时
// 都存在children
if (isDef(oldCh) && isDef(ch)) {
// 使用updateChildren来更新子节点
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly);
} else if (isDef(ch)) {
// 如果新的子节点存在,表示旧节点不需要了,如果旧节点是文本节点,将文本清除
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '');
// 通过addVnodes将新节点插入到新节点elm下
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
// 如果老的子节点存在,则表明更新的是空节点,需要将旧的节点通通删掉
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
} else if (isDef(oldVnode.text)) {
// 当旧节点是文本节点的时候,清除其节点文本内容
nodeOps.setTextContent(elm, '');
}
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text);
}
// 核心步骤4:执行postpatch钩子函数
if (isDef(data)) {
if (isDef((i = data.hook)) && isDef((i = i.postpatch))) i(oldVnode, vnode);
}
}
// prepatch是拿到新的vnode组件配置以及组件实例,去执行updateChildComponent方法
// src/core/vdom/create-component.js
const componentVNodeHooks = {
prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
const options = vnode.componentOptions;
const child = (vnode.componentInstance = oldVnode.componentInstance);
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
}
};
// src/core/instance/lifecycle.js
// 更新vm的一系列属性没比如slot,listener,props等更新
export function updateChildComponent(vm: Component, propsData: ?Object, listeners: ?Object, parentVnode: MountedComponentVNode, renderChildren: ?Array<VNode>) {
if (process.env.NODE_ENV !== 'production') {
isUpdatingChildComponent = true;
}
// determine whether component has slot children
// we need to do this before overwriting $options._renderChildren
const hasChildren = !!(
renderChildren || // has new static slots
vm.$options._renderChildren || // has old static slots
parentVnode.data.scopedSlots || // has new scoped slots
vm.$scopedSlots !== emptyObject
); // has old scoped slots
vm.$options._parentVnode = parentVnode;
vm.$vnode = parentVnode; // update vm's placeholder node without re-render
if (vm._vnode) {
// update child tree's parent
vm._vnode.parent = parentVnode;
}
vm.$options._renderChildren = renderChildren;
// update $attrs and $listeners hash
// these are also reactive so they may trigger child update if the child
// used them during render
vm.$attrs = parentVnode.data.attrs || emptyObject;
vm.$listeners = listeners || emptyObject;
// update props
if (propsData && vm.$options.props) {
toggleObserving(false);
const props = vm._props;
const propKeys = vm.$options._propKeys || [];
for (let i = 0; i < propKeys.length; i++) {
const key = propKeys[i];
const propOptions: any = vm.$options.props; // wtf flow?
props[key] = validateProp(key, propOptions, propsData, vm);
}
toggleObserving(true);
// keep a copy of raw propsData
vm.$options.propsData = propsData;
}
// update listeners
listeners = listeners || emptyObject;
const oldListeners = vm.$options._parentListeners;
vm.$options._parentListeners = listeners;
updateComponentListeners(vm, listeners, oldListeners);
// resolve slots + force update if has children
if (hasChildren) {
vm.$slots = resolveSlots(renderChildren, parentVnode.context);
vm.$forceUpdate();
}
if (process.env.NODE_ENV !== 'production') {
isUpdatingChildComponent = false;
}
}
// 递归patch vnode的过程
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
// 旧节点开始的位置
let oldStartIdx = 0;
// 新节点开始的位置
let newStartIdx = 0;
// 旧节点最后的位置
let oldEndIdx = oldCh.length - 1;
let oldStartVnode = oldCh[0];
let oldEndVnode = oldCh[oldEndIdx];
// 新节点最后的位置
let newEndIdx = newCh.length - 1;
let newStartVnode = newCh[0];
let newEndVnode = newCh[newEndIdx];
let oldKeyToIdx, idxInOld, vnodeToMove, refElm;
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
const canMove = !removeOnly;
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh);
}
// 如果旧节点开始位置小于等于旧节点最后位置,新节点开始位置小于等于新节点最后位置
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx];
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldStartVnode, newEndVnode)) {
// Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldEndVnode, newStartVnode)) {
// Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
if (isUndef(idxInOld)) {
// New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
} else {
vnodeToMove = oldCh[idxInOld];
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue);
oldCh[idxInOld] = undefined;
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
}
}
newStartVnode = newCh[++newStartIdx];
}
}
// 如果oldstartIndex>oldEndIdx 做插入动作
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
}
}
<template>
<div id="app">
<div>
<ul>
<li v-for="item in items" :key="item.id">{{ item.val }}</li>
</ul>
</div>
<button @click="change">change</button>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
items: [{ id: 0, val: 'A' }, { id: 1, val: 'B' }, { id: 2, val: 'C' }, { id: 3, val: 'D' }]
};
},
methods: {
change() {
this.items.reverse().push({ id: 4, val: 'E' });
}
}
};
</script>
初始化 vnode

执行 while 循环,走到 sameVnode(oldEndVnode, newStartVnode)

// Vnode moved left
//如果oldEndVnode与newStartVnode相等,移动oldEndVnode到最左边,并且让oldEndIdx--,让newStartIdx++
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
执行 while 循环,走到 sameVnode(oldEndVnode, newStartVnode)

// Vnode moved left
//如果oldEndVnode与newStartVnode相等,移动oldEndVnode到最左边,并且让oldEndIdx--,让newStartIdx++
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
步骤 4
执行 while 循环,走到 sameVnode(oldEndVnode, newStartVnode)

// Vnode moved left
//如果oldEndVnode与newStartVnode相等,移动oldEndVnode到最左边,并且让oldEndIdx--,让newStartIdx++
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
执行 while 循环,走到 sameVnode(oldStartVnode, newStartVnode)

//如果oldStartVnode与newStartVnode相等,并且让oldStartIdx++,让newStartIdx++,不移动vnode
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
不执行 while 循环,走到 if (oldStartIdx > oldEndIdx)

// 此时oldstartIndex>oldEndIdx 做插入动作,把newEndVnode插入到oldStartIndex位置
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
暂无评论
确认操作
请再次确认编辑图片
待上传图片 (剩余 0 张)
项目详情
确认评论邮箱
邀请制评论