虛擬DOM和Render函數


虛擬DOM

         虛擬DOM(下面簡化稱為Vnode)簡而言之 ,就是用js去描述一個dom節點樹,而DOM變化的對比,都放在js層來做。

傳統的dom節點,是這樣的

<div> <p className='text'>寫個啥內容啊</p> </div>
Vnode是長這樣的
{ nodeName:'div', //節點名字 attributes:{}, //屬性鍵值對 children:[], //子節點 key:undefined, //節點的唯一值 ... }

為什么需要Vnode?

  這里,我們來引入一個傳統的操作dom栗子。

var arr = [1,2,3,4] function render(data){ function createElement(tag){ var dom = document.createElement(tag) return dom } var ul= createElement('ui') data.forEach((elem)=>{ var liDom = createElement('li') liDom.innerHTML = elem ul.append(liDom) }) return ul } render(arr)

輸出打印的結果是:

 

 

 

 

但是這樣操作dom的結果,當項目越大,頁面交互越復雜,頻繁的去操作dom,會導致頁面卡頓,性能差,如何去減少dom操作是性能優化的一個關鍵點。

千呼萬喚的,Vnode可以解決這樣的問題!!!


Vnode是vue和react的核心。將DOM對比操作放在js層,提高效率。

如何使用Vnode?

首先vdom的兩個核心api
  • h函數:用於生成vnode
  • path函數:

h是指hyperscript,一種可以通過js來創建html的庫。

 
        
<div> <p className='text'>寫個啥內容啊</p> </div>
 
        
//經過babel編譯,然后將它們傳遞給h函數調用 h( 'div', null, h('p',{className:'text'},'寫個啥內容啊') ) //react的React.createElement函數的作用就跟這里的h函數一樣,結果是為了獲得一個vnode,虛擬節點

h函數輸出的元素是一個dom節點的js對象,類似這樣


{ 'nodeName':'div', 'attributes':{}, 'children':[...], 'key':undefined, ... }
h函數結束后,會調用render函數啦!!!


Render函數

前面我們提到了jsx是如何轉換為虛擬dom的js對象,那么虛擬dom又是如何轉為真實的DOM?

這里需要思考兩個問題:

  • render是什么?
  • 什么時候觸發render?
  • render 的過程發生了什么?

render是什么?

寫過React的人都知道,我們每個組件中有且只有一個render方法

//class方式創建的組件 class Home extends React.Component{ //省略 render(){ return ( <div> <p>一個節點</p> </div> ) } } // 函數申明創建的組件 function Page(){ return ( <div> <p>另一個節點</p> </div> ) } 

以上的代碼栗子容易看出,無論是class方式還是函數申明方式創建出來的組件,返回的有且只有一個頂點節點。調用render方法,可以將react元素渲染到真實的dom中。

什么時候觸發render?

在組件實例化和存在期時會執行render。

從下圖中可以看出:
  • 實例化過程中,當執行componentWillMount之后會執行render,開始將節點掛載在頁面上。
  • 存在期的過程中,setState會導致組件的重新渲染。
    componentWillReceiveProps => shouldComponentUpdate => componentWillUpdate => render => componentDidUpdate
      

 

React的重渲染機制,當狀態更新后,我們只想讓狀態相關的組件重新渲染,並不喜歡其他不相關的組件被重渲染,對此也有相關的優化操作。shouldComponentUpdate(nextProps,nextState)方法中是render函數調用前執行的函數,開發者可以通過nextProps,nextState參數來判斷當前場景是否需要重新渲染,當shouldComponentUpdate方法return true則重新渲染,return false則阻止組件渲染。

同樣,在PureComponent中,只接受props和state參數,如果props和state沒有改變,PureComponent不會重渲染,可以一定程度上減少了render帶來的消耗。

render 的過程發生了什么?

前面提到,React的核心虛擬DOM可以講真實的dom節點以obj對象的形式來表示,通過對比新舊的obj對象的差異,更改頁面相對應的變化節點。而React.render實際上就相當於是vdom里面的path函數,path函數接收兩個參數。

  • 當首次渲染的時候,調用的是path(container,vnode)
  • 更新渲染的時候,調用的是 path(vnode,newVnode)

 

以下例子,創建一個節點的實現思路(簡易的)

 

var vnode function render(data){ var newVnode = h(....)//前面章節提到h函數,執行后返回一個虛擬的js對象,用來描繪dom節點的 /* { tag:’div’, attrs: {id:’’}, children:[…] } */ if(vnode){ //如果節點已經存在,則重復渲染,將新舊節點傳入path函數中,新舊對比 path(vnode,newVnode) }else{ //如果節點不存在,則首次渲染,將節點掛在在根節點container上 path(container,newVnode) } // 將舊節點儲存起來,便於下次新節點的新舊對比 vnode = newVnode }
第一次渲染是如何進行?
path(container,newVnode)

// 創建一個真實節點 function createElement(vnode){ var tag = vnode.tag // 獲取虛擬節點的tag類型 var attrs = vnode.attrs|| [ ] // 儲存虛擬節點的屬性 var children = vnode.children || [] // 儲存虛擬節點的子節點 if(!tag){ return null } var elem = document.createElement(tag) // 創建一個真實的dom節點 for(attrName in attrs){ //遍歷所有屬性,給真實節點添加屬性 if(atrs.hasOwnProperty(attrName)){ elem.setAttribute(attrName,attrs[attrName]) } } children.forEach(function(childVode){ //遞歸虛擬節點的子節點,創建節點追加到父節點中 elem.appendChild(createElement(childVnode)) }) return elem }
再次渲染是如何進行?
    path(vnode,newVnode)
 
//更新渲染,通過對比新舊vnode,更新節點樹 function updateChildren (vnode,newVnode){ var children = vnode.children || [ ] var newChildren = newVnode.children || [ ] //遍歷所有的children children.forEach(function (child,index){ var newChild = newChildren[index] if(newChild==null){ return } if(child.tag === newChild.tag){ updateChildren(child,newChild) }else{ replaceNode(child,newChild) } }) }

path(container,vnode)和path(vnode,newVnode)的實現也是diff算法的一個實現過程,通過調用createElement和updateChildren方法讓頁面上的節點創建和更新。

當然,真正的diff算法是非常復雜的。

 

寫在最后

這一節的主要講的render函數在react中的一個工作過程,減少和控制不必要的重復渲染可以有效的提高頁面性能。

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 





































免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM