自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入理解react(源碼分析)

開(kāi)發(fā) 前端
首先理解ReactElement和ReactClass的概念。想要更好的利用react的虛擬DOM,diff算法的優(yōu)勢(shì),我們需要正確的優(yōu)化、組織react頁(yè)面。

理解ReactElement和ReactClass的概念

首先讓我們理解兩個(gè)概念:

ReactElement

一個(gè)描述DOM節(jié)點(diǎn)或component實(shí)例的字面級(jí)對(duì)象。它包含一些信息,包括組件類(lèi)型type和屬性props。就像一個(gè)描述DOM節(jié)點(diǎn)的元素(虛擬節(jié)點(diǎn))。它們可以被創(chuàng)建通過(guò)React.createElement方法或jsx寫(xiě)法

分為DOM Element和Component Elements兩類(lèi):

  • DOM Elements

當(dāng)節(jié)點(diǎn)的type屬性為字符串時(shí),它代表是普通的節(jié)點(diǎn),如div,span

  1.   type: 'button'
  2.   props: { 
  3.     className: 'button button-blue'
  4.     children: { 
  5.       type: 'b'
  6.       props: { 
  7.         children: 'OK!' 
  8.       } 
  9.     } 
  10.   } 
  11.  
  • Component Elements

當(dāng)節(jié)點(diǎn)的type屬性為一個(gè)函數(shù)或一個(gè)類(lèi)時(shí),它代表自定義的節(jié)點(diǎn)

  1. class Button extends React.Component { 
  2.   render() { 
  3.     const { children, color } = this.props; 
  4.     return { 
  5.       type: 'button'
  6.       props: { 
  7.         className: 'button button-' + color, 
  8.         children: { 
  9.           type: 'b'
  10.           props: { 
  11.             children: children 
  12.           } 
  13.         } 
  14.       } 
  15.     }; 
  16.   } 
  17.  
  18. // Component Elements 
  19.   type: Button, 
  20.   props: { 
  21.     color: 'blue'
  22.     children: 'OK!' 
  23.   } 
  24.  

ReactClass

ReactClass是平時(shí)我們寫(xiě)的Component組件(類(lèi)或函數(shù)),例如上面的Button類(lèi)。ReactClass實(shí)例化后調(diào)用render方法可返回DOM Element。

react渲染過(guò)程[[174578]] 

過(guò)程理解:

  1. // element是 Component Elements 
  2. ReactDOM.render({ 
  3.   type: Form, 
  4.   props: { 
  5.     isSubmitted: false
  6.     buttonText: 'OK!' 
  7.   } 
  8. }, document.getElementById('root')); 
  1. 調(diào)用React.render方法,將我們的element根虛擬節(jié)點(diǎn)渲染到container元素中。element可以是一個(gè)字符串文本元素,也可以是如上介紹的ReactElement(分為DOM Elements, Component Elements)。
  2. 根據(jù)element的類(lèi)型不同,分別實(shí)例化ReactDOMTextComponent, ReactDOMComponent, ReactCompositeComponent類(lèi)。這些類(lèi)用來(lái)管理ReactElement,負(fù)責(zé)將不同的ReactElement轉(zhuǎn)化成DOM(mountComponent方法),負(fù)責(zé)更新DOM(receiveComponent方法,updateComponent方法, 如下會(huì)介紹)等。
  3. ReactCompositeComponent實(shí)例調(diào)用mountComponent方法后內(nèi)部調(diào)用render方法,返回了DOM Elements。再對(duì)如圖的步驟2⃣遞歸。

react更新機(jī)制

每個(gè)類(lèi)型的元素都要處理好自己的更新:

  1. 自定義元素的更新,主要是更新render出的節(jié)點(diǎn),做甩手掌柜交給render出的節(jié)點(diǎn)的對(duì)應(yīng)component去管理更新。
  2. text節(jié)點(diǎn)的更新很簡(jiǎn)單,直接更新文案。
  3. 瀏覽器基本元素的更新,分為兩塊:
  • 先是更新屬性,對(duì)比出前后屬性的不同,局部更新。并且處理特殊屬性,比如事件綁定。
  • 然后是子節(jié)點(diǎn)的更新,子節(jié)點(diǎn)更新主要是找出差異對(duì)象,找差異對(duì)象的時(shí)候也會(huì)使用上面的shouldUpdateReactComponent來(lái)判斷,如果是可以直接更新的就會(huì)遞歸調(diào)用子節(jié)點(diǎn)的更新,這樣也會(huì)遞歸查找差異對(duì)象。不可直接更新的刪除之前的對(duì)象或添加新的對(duì)象。之后根據(jù)差異對(duì)象操作dom元素(位置變動(dòng),刪除,添加等)。

***步:調(diào)用this.setState

  1. ReactClass.prototype.setState = function(newState) { 
  2.     //this._reactInternalInstance是ReactCompositeComponent的實(shí)例 
  3.     this._reactInternalInstance.receiveComponent(null, newState); 
  4.  

第二步:調(diào)用內(nèi)部receiveComponent方法

這里主要分三種情況,文本元素,基本元素,自定義元素。

自定義元素:

receiveComponent方法源碼 

  1. // receiveComponent方法 
  2. ReactCompositeComponent.prototype.receiveComponent = function(nextElement, transaction, nextContext) { 
  3.     var prevElement = this._currentElement; 
  4.     var prevContext = this._context; 
  5.  
  6.     this._pendingElement = null
  7.  
  8.     this.updateComponent( 
  9.       transaction
  10.       prevElement, 
  11.       nextElement, 
  12.       prevContext, 
  13.       nextContext 
  14.     ); 
  15.  
  16.  

updateComponent方法源碼

  1. // updateComponent方法 
  2. ReactCompositeComponent.prototype.updateComponent = function
  3.     transaction
  4.     prevParentElement, 
  5.     nextParentElement, 
  6.     prevUnmaskedContext, 
  7.     nextUnmaskedContext 
  8. ) { 
  9.    // 簡(jiǎn)寫(xiě)..... 
  10.     
  11.     // 不是state更新而是props更新 
  12.     if (prevParentElement !== nextParentElement) { 
  13.       willReceive = true
  14.     } 
  15.  
  16.     if (willReceive && inst.componentWillReceiveProps) { 
  17.         // 調(diào)用生命周期componentWillReceiveProps方法 
  18.     } 
  19.      
  20.     // 是否更新元素 
  21.     if (inst.shouldComponentUpdate) { 
  22.         // 如果提供shouldComponentUpdate方法 
  23.         shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext); 
  24.     } else { 
  25.         if (this._compositeType === CompositeTypes.PureClass) { 
  26.           // 如果是PureClass,淺層對(duì)比props和state 
  27.           shouldUpdate = 
  28.             !shallowEqual(prevProps, nextProps) || 
  29.             !shallowEqual(inst.state, nextState); 
  30.         } 
  31.     } 
  32.      
  33.     if (shouldUpdate) { 
  34.       // 更新元素 
  35.       this._performComponentUpdate( 
  36.         nextParentElement, 
  37.         nextProps, 
  38.         nextState, 
  39.         nextContext, 
  40.         transaction
  41.         nextUnmaskedContext 
  42.       ); 
  43.     } else { 
  44.       // 不更新元素,但仍然設(shè)置props和state 
  45.       this._currentElement = nextParentElement; 
  46.       this._context = nextUnmaskedContext; 
  47.       inst.props = nextProps; 
  48.       inst.state = nextState; 
  49.       inst.context = nextContext; 
  50.     } 
  51.         
  52.    // ....... 
  53.  
  54.  

內(nèi)部_performComponentUpdate方法源碼

  1. function shouldUpdateReactComponent(prevElement, nextElement){ 
  2.   var prevEmpty = prevElement === null || prevElement === false
  3.   var nextEmpty = nextElement === null || nextElement === false
  4.   if (prevEmpty || nextEmpty) { 
  5.     return prevEmpty === nextEmpty; 
  6.   } 
  7.  
  8.   var prevType = typeof prevElement; 
  9.   var nextType = typeof nextElement; 
  10.    
  11.   if (prevType === 'string' || prevType === 'number') { 
  12.     // 如果先前的ReactElement對(duì)象類(lèi)型是字符串或數(shù)字,新的ReactElement對(duì)象類(lèi)型也是字符串或數(shù)字,則需要更新,新的ReactElement對(duì)象類(lèi)型是對(duì)象,則不應(yīng)該更新,直接替換。 
  13.     return (nextType === 'string' || nextType === 'number'); 
  14.   } else { 
  15.       // 如果先前的ReactElement對(duì)象類(lèi)型是對(duì)象,新的ReactElement對(duì)象類(lèi)型也是對(duì)象,并且標(biāo)簽類(lèi)型和key值相同,則需要更新 
  16.     return ( 
  17.       nextType === 'object' && 
  18.       prevElement.type === nextElement.type && 
  19.       prevElement.key === nextElement.key 
  20.     ); 
  21.   } 
  22.  

文本元素:

receiveComponent方法源碼

  1. ReactDOMTextComponent.prototype.receiveComponent(nextText, transaction) { 
  2.      //跟以前保存的字符串比較 
  3.     if (nextText !== this._currentElement) { 
  4.       this._currentElement = nextText; 
  5.       var nextStringText = '' + nextText; 
  6.       if (nextStringText !== this._stringText) { 
  7.         this._stringText = nextStringText; 
  8.         var commentNodes = this.getHostNode(); 
  9.         // 替換文本元素 
  10.         DOMChildrenOperations.replaceDelimitedText( 
  11.           commentNodes[0], 
  12.           commentNodes[1], 
  13.           nextStringText 
  14.         ); 
  15.       } 
  16.     } 
  17.   } 

基本元素:

receiveComponent方法源碼

  1. ReactDOMComponent.prototype.receiveComponent = function(nextElement, transaction, context) { 
  2.     var prevElement = this._currentElement; 
  3.     this._currentElement = nextElement; 
  4.     this.updateComponent(transaction, prevElement, nextElement, context); 
  5.  

updateComponent方法源碼

  1. ReactDOMComponent.prototype.updateComponent = function(transaction, prevElement, nextElement, context) { 
  2.     // 略..... 
  3.     //需要單獨(dú)的更新屬性 
  4.     this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag); 
  5.     //再更新子節(jié)點(diǎn) 
  6.     this._updateDOMChildren( 
  7.       lastProps, 
  8.       nextProps, 
  9.       transaction
  10.       context 
  11.     ); 
  12.  
  13.     // ...... 
  14.  

this._updateDOMChildren方法內(nèi)部調(diào)用diff算法,請(qǐng)看下一節(jié)........

react Diff算法

 diff算法源碼

  1. _updateChildren: function(nextNestedChildrenElements, transaction, context) { 
  2.     var prevChildren = this._renderedChildren; 
  3.     var removedNodes = {}; 
  4.     var mountImages = []; 
  5.      
  6.     // 獲取新的子元素?cái)?shù)組 
  7.     var nextChildren = this._reconcilerUpdateChildren( 
  8.       prevChildren, 
  9.       nextNestedChildrenElements, 
  10.       mountImages, 
  11.       removedNodes, 
  12.       transaction
  13.       context 
  14.     ); 
  15.      
  16.     if (!nextChildren && !prevChildren) { 
  17.       return
  18.     } 
  19.      
  20.     var updates = null
  21.     var name
  22.     var nextIndex = 0; 
  23.     var lastIndex = 0; 
  24.     var nextMountIndex = 0; 
  25.     var lastPlacedNode = null
  26.  
  27.     for (name in nextChildren) { 
  28.       if (!nextChildren.hasOwnProperty(name)) { 
  29.         continue
  30.       } 
  31.       var prevChild = prevChildren && prevChildren[name]; 
  32.       var nextChild = nextChildren[name]; 
  33.       if (prevChild === nextChild) { 
  34.           // 同一個(gè)引用,說(shuō)明是使用的同一個(gè)component,所以我們需要做移動(dòng)的操作 
  35.           // 移動(dòng)已有的子節(jié)點(diǎn) 
  36.           // NOTICE:這里根據(jù)nextIndex, lastIndex決定是否移動(dòng) 
  37.         updates = enqueue( 
  38.           updates, 
  39.           this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex) 
  40.         ); 
  41.          
  42.         // 更新lastIndex 
  43.         lastIndex = Math.max(prevChild._mountIndex, lastIndex); 
  44.         // 更新component的.mountIndex屬性 
  45.         prevChild._mountIndex = nextIndex; 
  46.          
  47.       } else { 
  48.         if (prevChild) { 
  49.           // 更新lastIndex 
  50.           lastIndex = Math.max(prevChild._mountIndex, lastIndex); 
  51.         } 
  52.          
  53.         // 添加新的子節(jié)點(diǎn)在指定的位置上 
  54.         updates = enqueue( 
  55.           updates, 
  56.           this._mountChildAtIndex( 
  57.             nextChild, 
  58.             mountImages[nextMountIndex], 
  59.             lastPlacedNode, 
  60.             nextIndex, 
  61.             transaction
  62.             context 
  63.           ) 
  64.         ); 
  65.          
  66.          
  67.         nextMountIndex++; 
  68.       } 
  69.        
  70.       // 更新nextIndex 
  71.       nextIndex++; 
  72.       lastPlacedNode = ReactReconciler.getHostNode(nextChild); 
  73.     } 
  74.      
  75.     // 移除掉不存在的舊子節(jié)點(diǎn),和舊子節(jié)點(diǎn)和新子節(jié)點(diǎn)不同的舊子節(jié)點(diǎn) 
  76.     for (name in removedNodes) { 
  77.       if (removedNodes.hasOwnProperty(name)) { 
  78.         updates = enqueue( 
  79.           updates, 
  80.           this._unmountChild(prevChildren[name], removedNodes[name]) 
  81.         ); 
  82.       } 
  83.     } 
  84.   }  

react的優(yōu)點(diǎn)與總結(jié)

優(yōu)點(diǎn)

  • 虛擬節(jié)點(diǎn)。在UI方面,不需要立刻更新視圖,而是生成虛擬DOM后統(tǒng)一渲染。
  • 組件機(jī)制。各個(gè)組件獨(dú)立管理,層層嵌套,互不影響,react內(nèi)部實(shí)現(xiàn)的渲染功能。
  • 差異算法。根據(jù)基本元素的key值,判斷是否遞歸更新子節(jié)點(diǎn),還是刪除舊節(jié)點(diǎn),添加新節(jié)點(diǎn)。

總結(jié)

想要更好的利用react的虛擬DOM,diff算法的優(yōu)勢(shì),我們需要正確的優(yōu)化、組織react頁(yè)面。例如將一個(gè)頁(yè)面render的ReactElement節(jié)點(diǎn)分解成多個(gè)組件。在需要優(yōu)化的組件手動(dòng)添加 shouldComponentUpdate 來(lái)避免不需要的 re-render。

責(zé)任編輯:龐桂玉 來(lái)源: segmentfault
相關(guān)推薦

2018-07-09 15:11:14

Java逃逸JVM

2010-06-01 15:25:27

JavaCLASSPATH

2016-12-08 15:36:59

HashMap數(shù)據(jù)結(jié)構(gòu)hash函數(shù)

2020-07-21 08:26:08

SpringSecurity過(guò)濾器

2015-03-17 09:44:08

2013-09-22 14:57:19

AtWood

2009-09-25 09:14:35

Hibernate日志

2023-10-19 11:12:15

Netty代碼

2021-02-17 11:25:33

前端JavaScriptthis

2020-09-23 10:00:26

Redis數(shù)據(jù)庫(kù)命令

2017-01-10 08:48:21

2017-08-15 13:05:58

Serverless架構(gòu)開(kāi)發(fā)運(yùn)維

2019-06-25 10:32:19

UDP編程通信

2024-02-21 21:14:20

編程語(yǔ)言開(kāi)發(fā)Golang

2024-01-29 15:54:41

Java線程池公平鎖

2021-09-08 06:51:52

AndroidRetrofit原理

2021-10-15 09:19:17

AndroidSharedPrefe分析源碼

2021-08-24 07:53:28

AndroidActivity生命周期

2022-11-11 10:48:55

AQS源碼架構(gòu)

2022-11-04 09:43:05

Java線程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)