深入理解react(源碼分析)
-
理解ReactElement和ReactClass的概念
-
ReactElement
-
ReactClass
-
-
react渲染過程
-
react更新機制
-
reactdiff算法
-
react的優點與總結
</ul>
-
DOM Elements
理解ReactElement和ReactClass的概念
首先讓我們理解兩個概念:
ReactElement
一個描述DOM節點或component實例的字面級對象。它包含一些信息,包括組件類型 type 和屬性 props 。就像一個描述DOM節點的元素(虛擬節點)。它們可以被創建通過 React.createElement 方法或 jsx 寫法
分為 DOM Element 和 Component 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!'
}
}</code></pre>
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 。再對如圖的步驟:two:遞歸。
react更新機制

每個類型的元素都要處理好自己的更新:
-
自定義元素的更新,主要是更新render出的節點,做甩手掌柜交給render出的節點的對應component去管理更新。
-
text節點的更新很簡單,直接更新文案。
-
瀏覽器基本元素的更新,分為兩塊:
-
先是更新屬性,對比出前后屬性的不同,局部更新。并且處理特殊屬性,比如事件綁定。
-
然后是子節點的更新,子節點更新主要是找出差異對象,找差異對象的時候也會使用上面的shouldUpdateReactComponent來判斷,如果是可以直接更新的就會遞歸調用子節點的更新,這樣也會遞歸查找差異對象。不可直接更新的刪除之前的對象或添加新的對象。之后根據差異對象操作dom元素(位置變動,刪除,添加等)。
</li>
</ol>
第一步:調用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
);
}</code></pre>
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;
}
// .......
}</code></pre>
內部_performComponentUpdate方法
// 內部_updateRenderedComponentWithNextElement方法
ReactCompositeComponent.prototype._updateRenderedComponentWithNextElement = function() {
// 判定兩個element需不需要更新
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
// 如果需要更新,就繼續調用子節點的receiveComponent的方法,傳入新的element更新子節點。
ReactReconciler.receiveComponent(
prevComponentInstance,
nextRenderedElement,
transaction,
this._processChildContext(context)
);
} else {
// 卸載之前的子節點,安裝新的子節點
var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
ReactReconciler.unmountComponent(
prevComponentInstance,
safely,
false /* skipLifecycle */
);
var nodeType = ReactNodeTypes.getType(nextRenderedElement);
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(
nextRenderedElement,
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
);
this._renderedComponent = child;
var nextMarkup = ReactReconciler.mountComponent(
child,
transaction,
this._hostParent,
this._hostContainerInfo,
this._processChildContext(context),
debugID
);
}</code></pre>
shouldUpdateReactComponent函數
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 。
來自:https://segmentfault.com/a/1190000007252756