您好,登錄后才能下訂單哦!
這篇文章給大家介紹怎樣深入理解react,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
理解ReactElement和ReactClass的概念
首先讓我們理解兩個概念:
ReactElement
一個描述DOM節點或component實例的字面級對象。它包含一些信息,包括組件類型type和屬性props。就像一個描述DOM節點的元素(虛擬節點)。它們可以被創建通過React.createElement方法或jsx寫法
分為DOM Element和Component Elements兩類:
DOM Elements
當節點的type屬性為字符串時,它代表是普通的節點,如div,span
{ type: 'button', props: { className: 'button button-blue', children: { type: 'b', props: { children: 'OK!' } } } }
Component Elements
當節點的type屬性為一個函數或一個類時,它代表自定義的節點
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是平時我們寫的Component組件(類或函數),例如上面的Button類。ReactClass實例化后調用render方法可返回DOM Element。
react渲染過程
過程理解:
// element是 Component Elements ReactDOM.render({ type: Form, props: { isSubmitted: false, buttonText: 'OK!' } }, document.getElementById('root'));
調用React.render方法,將我們的element根虛擬節點渲染到container元素中。element可以是一個字符串文本元素,也可以是如上介紹的ReactElement(分為DOM Elements, Component Elements)。
根據element的類型不同,分別實例化ReactDOMTextComponent, ReactDOMComponent, ReactCompositeComponent類。這些類用來管理ReactElement,負責將不同的ReactElement轉化成DOM(mountComponent方法),負責更新DOM(receiveComponent方法,updateComponent方法, 如下會介紹)等。
ReactCompositeComponent實例調用mountComponent方法后內部調用render方法,返回了DOM Elements。再對如圖的步驟2?遞歸。
react更新機制
每個類型的元素都要處理好自己的更新:
自定義元素的更新,主要是更新render出的節點,做甩手掌柜交給render出的節點的對應component去管理更新。
text節點的更新很簡單,直接更新文案。
瀏覽器基本元素的更新,分為兩塊:
先是更新屬性,對比出前后屬性的不同,局部更新。并且處理特殊屬性,比如事件綁定。
然后是子節點的更新,子節點更新主要是找出差異對象,找差異對象的時候也會使用上面的shouldUpdateReactComponent來判斷,如果是可以直接更新的就會遞歸調用子節點的更新,這樣也會遞歸查找差異對象。不可直接更新的刪除之前的對象或添加新的對象。之后根據差異對象操作dom元素(位置變動,刪除,添加等)。
***步:調用this.setState
ReactClass.prototype.setState = function(newState) { //this._reactInternalInstance是ReactCompositeComponent的實例 this._reactInternalInstance.receiveComponent(null, newState); }
第二步:調用內部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) { // 調用生命周期componentWillReceiveProps方法 } // 是否更新元素 if (inst.shouldComponentUpdate) { // 如果提供shouldComponentUpdate方法 shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext); } else { if (this._compositeType === CompositeTypes.PureClass) { // 如果是PureClass,淺層對比props和state shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState); } } if (shouldUpdate) { // 更新元素 this._performComponentUpdate( nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext ); } else { // 不更新元素,但仍然設置props和state this._currentElement = nextParentElement; this._context = nextUnmaskedContext; inst.props = nextProps; inst.state = nextState; inst.context = nextContext; } // ....... }
內部_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對象類型是字符串或數字,新的ReactElement對象類型也是字符串或數字,則需要更新,新的ReactElement對象類型是對象,則不應該更新,直接替換。 return (nextType === 'string' || nextType === 'number'); } else { // 如果先前的ReactElement對象類型是對象,新的ReactElement對象類型也是對象,并且標簽類型和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) { // 略..... //需要單獨的更新屬性 this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag); //再更新子節點 this._updateDOMChildren( lastProps, nextProps, transaction, context ); // ...... }
this._updateDOMChildren方法內部調用diff算法,請看下一節........
react Diff算法
diff算法源碼
_updateChildren: function(nextNestedChildrenElements, transaction, context) { var prevChildren = this._renderedChildren; var removedNodes = {}; var mountImages = []; // 獲取新的子元素數組 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) { // 同一個引用,說明是使用的同一個component,所以我們需要做移動的操作 // 移動已有的子節點 // NOTICE:這里根據nextIndex, lastIndex決定是否移動 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); } // 添加新的子節點在指定的位置上 updates = enqueue( updates, this._mountChildAtIndex( nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context ) ); nextMountIndex++; } // 更新nextIndex nextIndex++; lastPlacedNode = ReactReconciler.getHostNode(nextChild); } // 移除掉不存在的舊子節點,和舊子節點和新子節點不同的舊子節點 for (name in removedNodes) { if (removedNodes.hasOwnProperty(name)) { updates = enqueue( updates, this._unmountChild(prevChildren[name], removedNodes[name]) ); } } }
react的優點與總結
優點
虛擬節點。在UI方面,不需要立刻更新視圖,而是生成虛擬DOM后統一渲染。
組件機制。各個組件獨立管理,層層嵌套,互不影響,react內部實現的渲染功能。
差異算法。根據基本元素的key值,判斷是否遞歸更新子節點,還是刪除舊節點,添加新節點。
想要更好的利用react的虛擬DOM,diff算法的優勢,我們需要正確的優化、組織react頁面。例如將一個頁面render的ReactElement節點分解成多個組件。在需要優化的組件手動添加 shouldComponentUpdate 來避免不需要的 re-render。
關于怎樣深入理解react就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。