• Vue.js中render函数的理解


     vue2.0与1.0最大的区别就是使用了vDom。vDom的意思是虚拟Dom(Virtual Dom)。它是vue与react的核心。

      VirtualDom:VDom并不是一个真正意义上的Dom,简单来说,就是用JS来模拟Dom。当Dom前后发生改变时,把这个改变对比让js来做(这个对比即为有名的Diff算法),比较之后,只更新需要改变的Dom,不需要改变的地方不动,不需要全部重绘,用这种方法来提高渲染效率。它的存在是很有必要的,因为操作Dom是非常耗时的,而浏览器解析js是非常迅速的,通过虚拟Dom,可以省下很多时间。

      VDom的实现:snabbdom    链接:https://github.com/snabbdom

    它实现了虚拟Dom,包含diff算法。核心API:h函数,patch函数。真实的节点首先被转化成vNode(在vue中使用render函数转化)。

      h函数负责包装节点,它的格式是:

        h('标签', {属性}, [子元素]),子元素也是此格式,层层递归到最终的子节点。

      patch函数负责将vDom渲染成真实Dom,它的格式是:

        patch(container,vnode)首次渲染时

        patch(oldvnode,newvnode)当Dom有变化时

    patch源码:

    return function patch(oldVnode, vnode) {
     var i, elm, parent;
     //记录被插入的vnode队列,用于批触发insert
     var insertedVnodeQueue = [];
     //调用全局pre钩子
     for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
     //如果oldvnode是dom节点,转化为oldvnode
     if (isUndef(oldVnode.sel)) {
     oldVnode = emptyNodeAt(oldVnode);
     }
     //如果oldvnode与vnode相似,进行更新
     if (sameVnode(oldVnode, vnode)) {
     patchVnode(oldVnode, vnode, insertedVnodeQueue);
     } else {
     //否则,将vnode插入,并将oldvnode从其父节点上直接删除
     elm = oldVnode.elm;
     parent = api.parentNode(elm);
     
     createElm(vnode, insertedVnodeQueue);
     
     if (parent !== null) {
     api.insertBefore(parent, vnode.elm, api.nextSibling(elm));
     removeVnodes(parent, [oldVnode], 0, 0);
     }
     }
     //插入完后,调用被插入的vnode的insert钩子
     for (i = 0; i < insertedVnodeQueue.length; ++i) {
     insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]);
     }
     //然后调用全局下的post钩子
     for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
     //返回vnode用作下次patch的oldvnode
     return vnode;
     };

    patch方法里面实现了snabbdom 作为一个高效virtual dom库的法宝—高效的diff算法。diff算法是为了找出需要更新的节点,核心逻辑主要包含在updateChildren函数中。它的比较只会在同层级进行, 不会跨层级比较。在代码中展现:

    updateChildren源码:

    function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
     var oldStartIdx = 0, newStartIdx = 0;
     var oldEndIdx = oldCh.length - 1;
     var oldStartVnode = oldCh[0];
     var oldEndVnode = oldCh[oldEndIdx];
     var newEndIdx = newCh.length - 1;
     var newStartVnode = newCh[0];
     var newEndVnode = newCh[newEndIdx];
     var oldKeyToIdx;
     var idxInOld;
     var elmToMove;
     var before;
     while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
     if (oldStartVnode == null) {
     oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
     }
     else if (oldEndVnode == null) {
     oldEndVnode = oldCh[--oldEndIdx];
     }
     else if (newStartVnode == null) {
     newStartVnode = newCh[++newStartIdx];
     }
     else if (newEndVnode == null) {
     newEndVnode = newCh[--newEndIdx];
     }
     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)) {
     patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
     api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));
     oldStartVnode = oldCh[++oldStartIdx];
     newEndVnode = newCh[--newEndIdx];
     }
     else if (sameVnode(oldEndVnode, newStartVnode)) {
     patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
     api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
     oldEndVnode = oldCh[--oldEndIdx];
     newStartVnode = newCh[++newStartIdx];
     }
     else {
     if (oldKeyToIdx === undefined) {
      oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
     }
     idxInOld = oldKeyToIdx[newStartVnode.key];
     if (isUndef(idxInOld)) {
      api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
      newStartVnode = newCh[++newStartIdx];
     }
     else {
      elmToMove = oldCh[idxInOld];
      if (elmToMove.sel !== newStartVnode.sel) {
      api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
      }
      else {
      patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
      oldCh[idxInOld] = undefined;
      api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);
      }
      newStartVnode = newCh[++newStartIdx];
     }
     }
     }
     if (oldStartIdx > oldEndIdx) {
     before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
     addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
     }
     else if (newStartIdx > newEndIdx) {
     removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
     }
     }

    由此,可总结出虚拟Dom的运行过程(vue中)

     

    render函数:总结来说,使用render函数我们可以用js语言来构建Dom。构建时,vue提供了createElement函数。首先来看官网上对createElement的定义:

    // @returns {VNode}
    createElement(
      // {String | Object | Function}
      // 一个 HTML 标签字符串,组件选项对象,或者一个函数
      //必须return上述其中一个
      // 解析上述任何一种的一个 async 异步函数,必要参数。
      'div',
    
      // {Object}
      // 一个包含模板相关属性的数据对象
      // 这样,您可以在 template 中使用这些属性。可选参数。
      {
    
      },
    
      // {String | Array}
      // 子节点 (VNodes),由 `createElement()` 构建而成,
      // 或使用字符串来生成“文本节点”。可选参数。
      [
        '先写一些文字',
        createElement('h1', '一则头条'),
        createElement(MyComponent, {
          props: {
            someProp: 'foobar'
          }
        })
      ]
    )

    它有三个参数,第一个参数必选,可以是一个HTML标签,也可以是一个组件或者函数;第二个是可选参数,数据对象,在template中使用。第三个是子节点,也是可选参数,用法一致。在很多实际场景下,使用template的写法会比render函数简洁而且可读性高,所以要在合适的场合使用,不要增加负担。

    关于render函数的具体使用方法:https://cn.vuejs.org/v2/guide/render-function.html

  • 相关阅读:
    ls命令具有一个r选项,可以递归的列出子目录中的内容。请编写一个具有同样功能的程序。
    BizTalk Server 2006 正式发布了,有120天试用版可以下载 无为而为
    IT人看《国富论》系列:第一篇之第四章:论货币的起源及其效用。UML是软件行业的货币 无为而为
    使用XPathNavigator和XPathExpression求出XPath的值,[源代码] 无为而为
    为XPath自定义函数(因为XPath1.0的函数非常有限)[附源代码下载] 无为而为
    IT人看《国富论》系列:第一篇之第二章:论分工的原由。分工其实是人类利己倾向的结果 无为而为
    非常令人沮丧的是,SQL 2005 发布的 Web EndPoint不支持匿名访问 无为而为
    Team Foundation Server Workgroup Edition 5 用户限制到底是如何限制的呢? 无为而为
    微软发布新的MSN ToolBar V2.5,包含桌面搜索,Outlook搜索,支持IE的选项卡浏览模式 无为而为
    IT人看《国富论》系列:第一篇之第三章:论分工受市场范围的限制。外国人都觊觎的中国市场到底大还是不大? 无为而为
  • 原文地址:https://www.cnblogs.com/tomatoto/p/10002343.html
Copyright © 2020-2023  润新知