zoukankan      html  css  js  c++  java
  • Vue如何用虚拟dom进行渲染view的

    前提

    vue版本:v2.5.17-beta.0

    触发render

    vue在数据更新后会自动触发view的render工作,其依赖于数据驱动;在数据驱动的工作下,每一个vue的data属性都被监听,并且在set触发时,派发事件,通知收集到的依赖,从而触发对应的操作,render工作就是其中的一个依赖,并且被每一个data属性所收集,因此每一个data属性改变后,都会触发render。

    vue更新监听

    看一段代码

    // 来自mountComponent函数
    updateComponent = function () {
      vm._update(vm._render(), hydrating);
    };
    
    new Watcher(vm, updateComponent, noop, {
      before: function before () {
        if (vm._isMounted) {
          callHook(vm, 'beforeUpdate');
        }
      }
    }, true /* isRenderWatcher */);

    updateComponent是更新组件的函数,内部调用vm._update,并且传参vm._render();

    • vm._render()返回了什么?看源码则得知返回了虚拟dom(VNode)
    • vm._update函数又做了什么事情?顾名思义,更新传入的vnode
    • 什么时候触发updateComponent函数?在任何vue的data属性更改值都会触发

    更新view

    阅读_update函数得知,最终调用了vm.__patch__方法,最终找到为createPatchFunction方法的返回值

    var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
    
    Vue.prototype.__patch__ = inBrowser ? patch : noop;

    接下来重点看createPatchFunction的返回函数patch.

    1. 如果新的vnode不存在,则注销旧的vnode
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
      return
    }
    1. 如果旧的vnode不存在,则创建新的vnode
    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true;
      createElm(vnode, insertedVnodeQueue);
    }
    1. 如果以上不成立,则新的vnode和oldVnode都存在.如果oldVnode不是真实的dom,则为虚拟dom节点,并且新老vnode相似,则进行diff算法
    var isRealElement = isDef(oldVnode.nodeType);
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
    }
    1. 如果新老vnode不同,则拿到oldVnode的父节点,辅助创建vnode的新节点
    var oldElm = oldVnode.elm;
    var 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)
    );

    以上的步骤发现,更新view时,重点进入到了patchVnode函数,因此下面进入patchVnode的函数阅读

    1. 如果新老node相等,则不做处理
    if (oldVnode === vnode) {
      return
    }
    1. 如果vnode不是文本节点则有几种情况
    if (isDef(oldCh) && isDef(ch)) {
      // 如果oldVnode和vnode的children都有值(组件层),并且不想等,则执行更新children流程
      if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
    } else if (isDef(ch)) {
      // 如果vnode的children有值,如果当前dom有文本则清空,
      // 并将oldVnode的dom作为父节点,创建新vnode的children节点
      if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); }
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
    } else if (isDef(oldCh)) {
      // 如果oldVnode存在children,但是新的没有,则删除oldVnode的children的vnode
      removeVnodes(elm, oldCh, 0, oldCh.length - 1);
    } else if (isDef(oldVnode.text)) {
      // 如果oldVnode有文本信息,则将dom的文本清空
      nodeOps.setTextContent(elm, '');
    }
    1. 如果vnode是文本节点, 则当新老节点文本不同,将dom的文本更新成新vnode的文本
    else if (oldVnode.text !== vnode.text) {
      nodeOps.setTextContent(elm, vnode.text);
    }

    patchVnode函数处理的情况梳理一下则为:

    1. 如果新老vnode相同,不作处理
    2. 如果新vnode是文本节点,并且新老文本不同,将dom更新为vnode的文本
    3. 如果新老vnode都有children,表示他们是组件层,则调用updateChildren去做组件层更新
    4. 如果新vnode是组件层,oldVnode不是,则将当前dom添加新vnode组件的子元素
    5. 如果oldVnode是组件层,新vnode不是,则操作dom,将oldVnode包含的子元素删除
    6. 如果新vnode是组件层,oldVnode是文本节点,则将dom的文本清空

    在patchVnode部分又浮现了一个新的函数:updateChildren,是在新老vnode都不是文本节点时调用的,这里就是vue的渲染机制的核心

    updateChildren

    updateChildren中将新老vnode的children进行的循环处理,每一次循环去判断是否有相同的vnode,如果不存在或存在但已经不相同则创建新的dom,否则,如果是新老节点相同,回调patchVnode函数去处理2个节点。 这样进行了递归处理,组件层的更新就完成了。

    结尾

    本文为看源码分析vue更新机制部分,省略了特殊场景的源码分析,比如ssr、静态组件等。

  • 相关阅读:
    Android-监听操作系统短信
    Android-Observer(内容观察者)
    Android-ContentProvider读取/新增/操作系统联系人数据
    Android-ContentProvider原理图
    Android-ContentProvider-UriMatcher
    Android-ListView-CursorAdapter
    Android-ListView-SimpleCursorAdapter
    openssl可以实现:秘钥证书管理、对称加密和非对称加密
    网址收藏
    php 通用数据库类
  • 原文地址:https://www.cnblogs.com/xujiazheng/p/12101764.html
Copyright © 2011-2022 走看看