Vue.js中render函數的理解

 vue2.0與1.0最大的區別就是使用了vDom。vDom的意思是虛擬Dom(Virtual Dom)。它是vue與react的核心。html

  VirtualDom:VDom並非一個真正意義上的Dom,簡單來講,就是用JS來模擬Dom。當Dom先後發生改變時,把這個改變對比讓js來作(這個對比即爲有名的Diff算法),比較以後,只更新須要改變的Dom,不須要改變的地方不動,不須要所有重繪,用這種方法來提升渲染效率。它的存在是頗有必要的,由於操做Dom是很是耗時的,而瀏覽器解析js是很是迅速的,經過虛擬Dom,能夠省下不少時間。vue

  VDom的實現:snabbdom    連接:https://github.com/snabbdomnode

它實現了虛擬Dom,包含diff算法。核心API:h函數,patch函數。真實的節點首先被轉化成vNode(在vue中使用render函數轉化)。react

  h函數負責包裝節點,它的格式是:git

    h('標籤', {屬性}, [子元素]),子元素也是此格式,層層遞歸到最終的子節點。github

  patch函數負責將vDom渲染成真實Dom,它的格式是:算法

    patch(container,vnode)首次渲染時api

    patch(oldvnode,newvnode)當Dom有變化時瀏覽器

patch源碼:dom

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

相關文章
相關標籤/搜索