国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

深入理解react(源碼分析)

Original 2016-11-10 15:42:01 889
abstract:理解ReactElement和ReactClass的概念首先讓我們理解兩個(gè)概念:ReactElement一個(gè)描述DOM節(jié)點(diǎn)或component實(shí)例的字面級(jí)對(duì)象。它包含一些信息,包括組件類型type和屬性props。就像一個(gè)描述DOM節(jié)點(diǎn)的元素(虛擬節(jié)點(diǎn))。它們可以被創(chuàng)建通過React.createElement方法或jsx寫法分為DOM Element和Component Elements兩類:D

理解ReactElement和ReactClass的概念

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

ReactElement

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

分為DOM Element和Component Elements兩類:

DOM Elements

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

{ 
  type: 'button', 
  props: { 
    className: 'button button-blue', 
    children: { 
      type: 'b', 
      props: { 
        children: 'OK!' 
      } 
    } 
  } 
}

Component Elements

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

class Button extends React.Component { 
  render() { 
    const { children, color } = this.props; 
    return { 
      type: 'button', 
      props: { 
        className: 'button button-' + color, 
        children: { 
          type: 'b', 
          props: { 
            children: children 
          } 
        } 
      } 
    }; 
  } 
} 
 
// Component Elements 
{ 
  type: Button, 
  props: { 
    color: 'blue', 
    children: 'OK!' 
  } 
}

ReactClass

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

react渲染過程

過程理解:

// element是 Component Elements 
ReactDOM.render({ 
  type: Form, 
  props: { 
    isSubmitted: false, 
    buttonText: 'OK!' 
  } 
}, document.getElementById('root'));

1.調(diào)用React.render方法,將我們的element根虛擬節(jié)點(diǎn)渲染到container元素中。element可以是一個(gè)字符串文本元素,也可以是如上介紹的ReactElement(分為DOM Elements, Component Elements)。

2.根據(jù)element的類型不同,分別實(shí)例化ReactDOMTextComponent, ReactDOMComponent, ReactCompositeComponent類。這些類用來管理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ī)制

wKioL1gQpJbxlvxAAAF_9XkIu9E153.png

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

1.自定義元素的更新,主要是更新render出的節(jié)點(diǎn),做甩手掌柜交給render出的節(jié)點(diǎn)的對(duì)應(yīng)component去管理更新。

2.text節(jié)點(diǎn)的更新很簡單,直接更新文案。

3.瀏覽器基本元素的更新,分為兩塊:

  • 先是更新屬性,對(duì)比出前后屬性的不同,局部更新。并且處理特殊屬性,比如事件綁定。

  • 然后是子節(jié)點(diǎn)的更新,子節(jié)點(diǎn)更新主要是找出差異對(duì)象,找差異對(duì)象的時(shí)候也會(huì)使用上面的shouldUpdateReactComponent來判斷,如果是可以直接更新的就會(huì)遞歸調(diào)用子節(jié)點(diǎn)的更新,這樣也會(huì)遞歸查找差異對(duì)象。不可直接更新的刪除之前的對(duì)象或添加新的對(duì)象。之后根據(jù)差異對(duì)象操作dom元素(位置變動(dòng),刪除,添加等)。

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

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

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

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

自定義元素:

receiveComponent方法源碼

// receiveComponent方法 
ReactCompositeComponent.prototype.receiveComponent = function(nextElement, transaction, nextContext) { 
    var prevElement = this._currentElement; 
    var prevContext = this._context; 
 
    this._pendingElement = null; 
 
    this.updateComponent( 
      transaction, 
      prevElement, 
      nextElement, 
      prevContext, 
      nextContext 
    ); 
 
}

updateComponent方法源碼

// updateComponent方法 
ReactCompositeComponent.prototype.updateComponent = function( 
    transaction, 
    prevParentElement, 
    nextParentElement, 
    prevUnmaskedContext, 
    nextUnmaskedContext 
) { 
   // 簡寫..... 
    
    // 不是state更新而是props更新 
    if (prevParentElement !== nextParentElement) { 
      willReceive = true; 
    } 
 
    if (willReceive && inst.componentWillReceiveProps) { 
        // 調(diào)用生命周期componentWillReceiveProps方法 
    } 
     
    // 是否更新元素 
    if (inst.shouldComponentUpdate) { 
        // 如果提供shouldComponentUpdate方法 
        shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext); 
    } else { 
        if (this._compositeType === CompositeTypes.PureClass) { 
          // 如果是PureClass,淺層對(duì)比props和state 
          shouldUpdate = 
            !shallowEqual(prevProps, nextProps) || 
            !shallowEqual(inst.state, nextState); 
        } 
    } 
     
    if (shouldUpdate) { 
      // 更新元素 
      this._performComponentUpdate( 
        nextParentElement, 
        nextProps, 
        nextState, 
        nextContext, 
        transaction, 
        nextUnmaskedContext 
      ); 
    } else { 
      // 不更新元素,但仍然設(shè)置props和state 
      this._currentElement = nextParentElement; 
      this._context = nextUnmaskedContext; 
      inst.props = nextProps; 
      inst.state = nextState; 
      inst.context = nextContext; 
    } 
        
   // ....... 
 
}

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

function shouldUpdateReactComponent(prevElement, nextElement){ 
  var prevEmpty = prevElement === null || prevElement === false; 
  var nextEmpty = nextElement === null || nextElement === false; 
  if (prevEmpty || nextEmpty) { 
    return prevEmpty === nextEmpty; 
  } 
 
  var prevType = typeof prevElement; 
  var nextType = typeof nextElement; 
   
  if (prevType === 'string' || prevType === 'number') { 
    // 如果先前的ReactElement對(duì)象類型是字符串或數(shù)字,新的ReactElement對(duì)象類型也是字符串或數(shù)字,則需要更新,新的ReactElement對(duì)象類型是對(duì)象,則不應(yīng)該更新,直接替換。 
    return (nextType === 'string' || nextType === 'number'); 
  } else { 
      // 如果先前的ReactElement對(duì)象類型是對(duì)象,新的ReactElement對(duì)象類型也是對(duì)象,并且標(biāo)簽類型和key值相同,則需要更新 
    return ( 
      nextType === 'object' && 
      prevElement.type === nextElement.type && 
      prevElement.key === nextElement.key 
    ); 
  } 
}

文本元素:

receiveComponent方法源碼

ReactDOMTextComponent.prototype.receiveComponent(nextText, transaction) { 
     //跟以前保存的字符串比較 
    if (nextText !== this._currentElement) { 
      this._currentElement = nextText; 
      var nextStringText = '' + nextText; 
      if (nextStringText !== this._stringText) { 
        this._stringText = nextStringText; 
        var commentNodes = this.getHostNode(); 
        // 替換文本元素 
        DOMChildrenOperations.replaceDelimitedText( 
          commentNodes[0], 
          commentNodes[1], 
          nextStringText 
        ); 
      } 
    } 
  }

基本元素:

receiveComponent方法源碼

ReactDOMComponent.prototype.receiveComponent = function(nextElement, transaction, context) { 
    var prevElement = this._currentElement; 
    this._currentElement = nextElement; 
    this.updateComponent(transaction, prevElement, nextElement, context); 
}

updateComponent方法源碼

ReactDOMComponent.prototype.updateComponent = function(transaction, prevElement, nextElement, context) { 
    // 略..... 
    //需要單獨(dú)的更新屬性 
    this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag); 
    //再更新子節(jié)點(diǎn) 
    this._updateDOMChildren( 
      lastProps, 
      nextProps, 
      transaction, 
      context 
    ); 
 
    // ...... 
}

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

react Diff算法

2.png

diff算法源碼

_updateChildren: function(nextNestedChildrenElements, transaction, context) { 
    var prevChildren = this._renderedChildren; 
    var removedNodes = {}; 
    var mountImages = []; 
     
    // 獲取新的子元素?cái)?shù)組 
    var nextChildren = this._reconcilerUpdateChildren( 
      prevChildren, 
      nextNestedChildrenElements, 
      mountImages, 
      removedNodes, 
      transaction, 
      context 
    ); 
     
    if (!nextChildren && !prevChildren) { 
      return; 
    } 
     
    var updates = null; 
    var name; 
    var nextIndex = 0; 
    var lastIndex = 0; 
    var nextMountIndex = 0; 
    var lastPlacedNode = null; 
 
    for (name in nextChildren) { 
      if (!nextChildren.hasOwnProperty(name)) { 
        continue; 
      } 
      var prevChild = prevChildren && prevChildren[name]; 
      var nextChild = nextChildren[name]; 
      if (prevChild === nextChild) { 
          // 同一個(gè)引用,說明是使用的同一個(gè)component,所以我們需要做移動(dòng)的操作 
          // 移動(dòng)已有的子節(jié)點(diǎn) 
          // NOTICE:這里根據(jù)nextIndex, lastIndex決定是否移動(dòng) 
        updates = enqueue( 
          updates, 
          this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex) 
        ); 
         
        // 更新lastIndex 
        lastIndex = Math.max(prevChild._mountIndex, lastIndex); 
        // 更新component的.mountIndex屬性 
        prevChild._mountIndex = nextIndex; 
         
      } else { 
        if (prevChild) { 
          // 更新lastIndex 
          lastIndex = Math.max(prevChild._mountIndex, lastIndex); 
        } 
         
        // 添加新的子節(jié)點(diǎn)在指定的位置上 
        updates = enqueue( 
          updates, 
          this._mountChildAtIndex( 
            nextChild, 
            mountImages[nextMountIndex], 
            lastPlacedNode, 
            nextIndex, 
            transaction, 
            context 
          ) 
        ); 
         
         
        nextMountIndex++; 
      } 
       
      // 更新nextIndex 
      nextIndex++; 
      lastPlacedNode = ReactReconciler.getHostNode(nextChild); 
    } 
     
    // 移除掉不存在的舊子節(jié)點(diǎn),和舊子節(jié)點(diǎn)和新子節(jié)點(diǎn)不同的舊子節(jié)點(diǎn) 
    for (name in removedNodes) { 
      if (removedNodes.hasOwnProperty(name)) { 
        updates = enqueue( 
          updates, 
          this._unmountChild(prevChildren[name], removedNodes[name]) 
        ); 
      } 
    } 
  }

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)勢,我們需要正確的優(yōu)化、組織react頁面。例如將一個(gè)頁面render的ReactElement節(jié)點(diǎn)分解成多個(gè)組件。在需要優(yōu)化的組件手動(dòng)添加 shouldComponentUpdate 來避免不需要的 re-render。


Release Notes

Popular Entries