oldEndVnode和newEndVnode相同时,不对dom进行移动
oldStartVnode和newEndVnode相同时,oldStartVnode.el移动到oldEndVnode.el后面
oldEndVnode和newStartVnode相同时,oldEndVnode.el移动到oldStartVnode.el前面
.newCh中的节点oldCh里没有,将新节点插入到oldStartVnode.el的前边
oldCh遍历完或newCh遍历完
oldCh遍历完(包含newCh同时遍历完),oldStartIdx > oldEndIdx,可以认为oldCh先遍历完。此时newStartIdx和newEndIdx之间的vnode是新增的,调用addVnodes,把他们全部插进before(before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;)的后边。
newStartIdx > newEndIdx,可以认为newCh先遍历完。此时oldStartIdx和oldEndIdx之间的vnode在新的子节点里已经不存在了,调用removeVnodes将它们从dom里删除
一个VNode的实例对象包含了以下属性
export default class VNode {
tag: string | void; //当前节点的标签名
data: VNodeData | void; //当前节点的数据对象
children: ?Array<VNode>; //包当前节点的子节点
text: string | void; //当前节点的文本
elm: Node | void; //当前虚拟节点对应的真实的dom节点
ns: string | void; //节点的namespace
context: Component | void; // rendered in this component's scope 编译作用域
key: string | number | void; //节点的key属性,用于作为节点的标识
componentOptions: VNodeComponentOptions | void; //创建组件实例时会用到的选项信息
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node 静态节点的标识
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder? 当前节点是否是注释节点
isCloned: boolean; // is a cloned node? 当前节点是否复制节点
isOnce: boolean; // is a v-once node? 当前节点是否有v-once指令
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes 函数化组件的作用域
fnOptions: ?ComponentOptions; // for SSR caching
fnScopeId: ?string; // functioanl scope id support
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
export const createEmptyVNode = (text: string = '') => {
const node = new VNode()
node.text = text
node.isComment = true
return node
}
export function createTextVNode (val: string | number) {
return new VNode(undefined, undefined, undefined, String(val))
}
// optimized shallow clone
// used for static nodes and slot nodes because they may be reused across
// multiple renders, cloning them avoids errors when DOM manipulations rely
// on their elm reference.
export function cloneVNode (vnode: VNode): VNode {
const cloned = new VNode(
vnode.tag,
vnode.data,
vnode.children,
vnode.text,
vnode.elm,
vnode.context,
vnode.componentOptions,
vnode.asyncFactory
)
cloned.ns = vnode.ns
cloned.isStatic = vnode.isStatic
cloned.key = vnode.key
cloned.isComment = vnode.isComment
cloned.fnContext = vnode.fnContext
cloned.fnOptions = vnode.fnOptions
cloned.fnScopeId = vnode.fnScopeId
cloned.isCloned = true
return cloned
// {[key:string]: any} 键值对定义,key为string类型
export interface VNodeData {
key?: string|number;
slot?: string;
scopedSlots?: {[key: string]: ScopedSlot};
ref?: string;
tag?: string;
staticClass?: string;
class?: any;
staticStyle?: {[key: string]: any};
style?: Object[]|Object;
props?: {[key: string]: any};
attrs?: {[key:string]: any};
domProps?: {[key: string]: any};
hook?: {[key: string]: Function};
on?: {[key:string]: Function|Function[]};
nativeOn?: {[key:string]: Function| Function[]};
transition?: Object;
show?: boolean;
inlineTemplate?: {
render:Function;
staticRenderFns: FUnction[];
};
directives?: VNodeDirective[];
keepAlive?: boolean;
}
如果vnode不存在但是oldVnode存在,说明意图是要销毁老节点,那么就调用invokeDestroyHook(oldVnode)来进行销毁
如果oldVnode不存在但是vnode存在,说明意图是要创建新节点,那么就调用createElm来创建新节点
当vnode和oldVnode都存在时
如果oldVnode和vnode是同一个节点,就调用patchVnode来进行patch
当vnode和oldVnode不是同一个节点时,如果oldVnode是真实dom节点或hydrating设置为true,需要用hydrate函数将虚拟dom和真是dom进行映射,然后将oldVnode设置为对应的虚拟dom,找到oldVnode.elm的父节点,根据vnode创建一个真实dom节点并插入到该父节点中oldVnode.elm的位置
如果oldVnode跟vnode完全一致,那么不需要做任何事情
如果oldVnode跟vnode都是静态节点,且具有相同的key,当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elm和oldVnode.child都复制到vnode上,也不用再有其他操作
如果vnode不是文本节点或注释节点
如果oldVnode和vnode都有子节点,且2方的子节点不完全一致,就执行更新子节点的操作(这一部分其实是在updateChildren函数中实现)
分别获取oldVnode和vnode的firstChild、lastChild,赋值给oldStartVnode、oldEndVnode、newStartVnode、newEndVnode
如果oldStartVnode和newStartVnode是同一节点,调用patchVnode进行patch,然后将oldStartVnode和newStartVnode都设置为下一个子节点;如果oldEndVnode和newEndVnode是同一节点,调用patchVnode进行patch,然后将oldEndVnode和newEndVnode都设置为上一个子节点重复上述流程
如果oldEndVnode和newEndVnode是同一节点,调用patchVnode进行patch,然后将oldEndVnode和newEndVnode都设置为上一个子节点,重复上述流程
如果oldStartVnode和newEndVnode是同一节点,调用patchVnode进行patch,如果removeOnly是false,那么可以把oldStartVnode.elm移动到oldEndVnode.elm之后,然后把oldStartVnode设置为下一个节点,newEndVnode设置为上一个节点,重复上述流程
如果newStartVnode和oldEndVnode是同一节点,调用patchVnode进行patch,如果removeOnly是false,那么可以把oldEndVnode.elm移动到oldStartVnode.elm之前,然后把newStartVnode设置为下一个节点,oldEndVnode设置为上一个节点,重复上述流程
如果以上都不匹配,就尝试在oldChildren中寻找跟newStartVnode具有相同key的节点,如果找不到相同key的节点,说明newStartVnode是一个新节点,就创建一个,然后把newStartVnode设置为下一个节点
如果上一步找到了跟newStartVnode相同key的节点,那么通过其他属性的比较来判断这2个节点是否是同一个节点,如果是,就调用patchVnode进行patch,如果removeOnly是false,就把newStartVnode.elm插入到oldStartVnode.elm之前,把newStartVnode设置为下一个节点,重复上述流程
如果在oldChildren中没有寻找到newStartVnode的同一节点,那就创建一个新节点,把newStartVnode设置为下一个节点,重复上述流程
如果oldStartVnode跟oldEndVnode重合了,并且newStartVnode跟newEndVnode也重合了,这个循环就结束了
如果只有oldVnode有子节点,那就把这些节点都删除
如果只有vnode有子节点,那就创建这些子节点
如果oldVnode和vnode都没有子节点,但是oldVnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串