首先理解ReactElement和ReactClass的概念。想要更好的利用react的虛擬DOM,diff算法的優點,咱們須要正確的優化、組織react頁面。react
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!' } }ReactElement
ReactClass是平時咱們寫的Component組件(類或函數),例如上面的Button類。ReactClass實例化後調用render方法可返回DOM Element。dom
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。
react更新機制
每一個類型的元素都要處理好本身的更新:優化
- 自定義元素的更新,主要是更新render出的節點,作甩手掌櫃交給render出的節點的對應component去管理更新。
- text節點的更新很簡單,直接更新文案。
- 瀏覽器基本元素的更新,分爲兩塊:
- 先是更新屬性,對比出先後屬性的不一樣,局部更新。而且處理特殊屬性,好比事件綁定。
- 而後是子節點的更新,子節點更新主要是找出差別對象,找差別對象的時候也會使用上面的shouldUpdateReactComponent來判斷,若是是能夠直接更新的就會遞歸調用子節點的更新,這樣也會遞歸查找差別對象。不可直接更新的刪除以前的對象或添加新的對象。以後根據差別對象操做dom元素(位置變更,刪除,添加等)。
第一步:調用this.setState
ReactClass.prototype.setState = function(newState) { //this._reactInternalInstance是ReactCompositeComponent的實例 this._reactInternalInstance.receiveComponent(null, newState); }第二步:調用內部receiveComponent方法
這裏主要分三種狀況,文本元素,基本元素,自定義元素。
自定義元素:
receiveComponent方法源碼:this// 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方法源碼spa
// 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方法源碼prototype
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對象類型也是字符串或數字, return (nextType === 'string' || nextType === 'number'); } else { // 若是先前的ReactElement對象類型是對象,新的ReactElement對象類型也是對象,而且標籤類型和key值相同,則須要更新 return ( nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key ); } }基本元素
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。