Virtual Dom原理解析

snabbdom

  1. oldStartVnode和newStartVnode相同时,不对dom进行移动
  2. oldEndVnode和newEndVnode相同时,不对dom进行移动

  3. oldStartVnode和newEndVnode相同时,oldStartVnode.el移动到oldEndVnode.el后面

  4. oldEndVnode和newStartVnode相同时,oldEndVnode.el移动到oldStartVnode.el前面

  5. .newCh中的节点oldCh里没有,将新节点插入到oldStartVnode.el的前边

  6. oldCh遍历完或newCh遍历完

    1. oldCh遍历完(包含newCh同时遍历完),oldStartIdx > oldEndIdx,可以认为oldCh先遍历完。此时newStartIdx和newEndIdx之间的vnode是新增的,调用addVnodes,把他们全部插进before(before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;)的后边。

    2. 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;

}

patch函数

  1. 如果vnode不存在但是oldVnode存在,说明意图是要销毁老节点,那么就调用invokeDestroyHook(oldVnode)来进行销毁

  2. 如果oldVnode不存在但是vnode存在,说明意图是要创建新节点,那么就调用createElm来创建新节点

  3. 当vnode和oldVnode都存在时

    1. 如果oldVnode和vnode是同一个节点,就调用patchVnode来进行patch

    2. 当vnode和oldVnode不是同一个节点时,如果oldVnode是真实dom节点或hydrating设置为true,需要用hydrate函数将虚拟dom和真是dom进行映射,然后将oldVnode设置为对应的虚拟dom,找到oldVnode.elm的父节点,根据vnode创建一个真实dom节点并插入到该父节点中oldVnode.elm的位置

patchVnode函数

  1. 如果oldVnode跟vnode完全一致,那么不需要做任何事情

  2. 如果oldVnode跟vnode都是静态节点,且具有相同的key,当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elm和oldVnode.child都复制到vnode上,也不用再有其他操作

  3. 如果vnode不是文本节点或注释节点

    1. 如果oldVnode和vnode都有子节点,且2方的子节点不完全一致,就执行更新子节点的操作(这一部分其实是在updateChildren函数中实现)

      1. 分别获取oldVnode和vnode的firstChild、lastChild,赋值给oldStartVnode、oldEndVnode、newStartVnode、newEndVnode

      2. 如果oldStartVnode和newStartVnode是同一节点,调用patchVnode进行patch,然后将oldStartVnode和newStartVnode都设置为下一个子节点;如果oldEndVnode和newEndVnode是同一节点,调用patchVnode进行patch,然后将oldEndVnode和newEndVnode都设置为上一个子节点重复上述流程

      3. 如果oldEndVnode和newEndVnode是同一节点,调用patchVnode进行patch,然后将oldEndVnode和newEndVnode都设置为上一个子节点,重复上述流程

      4. 如果oldStartVnode和newEndVnode是同一节点,调用patchVnode进行patch,如果removeOnly是false,那么可以把oldStartVnode.elm移动到oldEndVnode.elm之后,然后把oldStartVnode设置为下一个节点,newEndVnode设置为上一个节点,重复上述流程

      5. 如果newStartVnode和oldEndVnode是同一节点,调用patchVnode进行patch,如果removeOnly是false,那么可以把oldEndVnode.elm移动到oldStartVnode.elm之前,然后把newStartVnode设置为下一个节点,oldEndVnode设置为上一个节点,重复上述流程

      6. 如果以上都不匹配,就尝试在oldChildren中寻找跟newStartVnode具有相同key的节点,如果找不到相同key的节点,说明newStartVnode是一个新节点,就创建一个,然后把newStartVnode设置为下一个节点

      7. 如果上一步找到了跟newStartVnode相同key的节点,那么通过其他属性的比较来判断这2个节点是否是同一个节点,如果是,就调用patchVnode进行patch,如果removeOnly是false,就把newStartVnode.elm插入到oldStartVnode.elm之前,把newStartVnode设置为下一个节点,重复上述流程

      8. 如果在oldChildren中没有寻找到newStartVnode的同一节点,那就创建一个新节点,把newStartVnode设置为下一个节点,重复上述流程

      9. 如果oldStartVnode跟oldEndVnode重合了,并且newStartVnode跟newEndVnode也重合了,这个循环就结束了

    2. 如果只有oldVnode有子节点,那就把这些节点都删除

    3. 如果只有vnode有子节点,那就创建这些子节点

    4. 如果oldVnode和vnode都没有子节点,但是oldVnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串

results for ""

    No results matching ""