React中Virtual DOM幾乎涵蓋了全部的原生DOM。React大部分工做都是在Virtual DOM完成的。 ReactDOMComponent針對Virturl DOM主要進行了一下處理:html
當執行mountComponent時,ReactDOMComponent首先會生成標記和標籤。經過createOpenTagMarkupAndPutListeners(transaction) 來處理DOM節點的屬性和事件。node
源碼以下:react
//ReactDOMComponent.js
//_createOpenTagMarkupAndPutListeners處理DOM節點的屬性和事件
_createOpenTagMarkupAndPutListeners: function(transaction, props) {
//聲明ret變量,保存一個標籤
var ret = '<' + this._currentElement.type;
//循環拼湊出屬性
for (var propKey in props) {
//判斷屬性是不是props的實例屬性,若是不是則跳出
if (!props.hasOwnProperty(propKey)) {
continue;
}
var propValue = props[propKey];
if (propValue == null) {
continue;
}
if (registrationNameModules.hasOwnProperty(propKey)) {
if (propValue) {
//經過enqueuePutListener添加事件代理
enqueuePutListener(this, propKey, propValue, transaction);
}
} else {
if (propKey === STYLE) {
//若是是樣式屬性的話則合併樣式
if (propValue) {
propValue = this._previousStyleCopy = Object.assign({}, props.style);
}
//這裏調用CSSPropertyOperations.createMarkupForStyles
propValue = CSSPropertyOperations.createMarkupForStyles(propValue, this);
}
//這裏建立屬性,
var markup = null;
if (this._tag != null && isCustomComponent(this._tag, props)) {
if (!RESERVED_PROPS.hasOwnProperty(propKey)) {
markup = DOMPropertyOperations.createMarkupForCustomAttribute(propKey, propValue);
}
} else {
markup = DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
}
if (markup) {
ret += ' ' + markup;
}
}
}
// For static pages, no need to put React ID and checksum. Saves lots of
// bytes.
//對於靜態頁,不須要設置react-id
if (transaction.renderToStaticMarkup) {
return ret;
}
//這裏設置惟一標識
if (!this._nativeParent) {
ret += ' ' + DOMPropertyOperations.createMarkupForRoot();
}
ret += ' ' + DOMPropertyOperations.createMarkupForID(this._domID);
return ret;
},
複製代碼
當執行recevieComponent方法時,ReactDOMComponent會經過this.updateComponent來更新DOM節點屬性。bash
- 若是不須要舊樣式,則遍歷舊樣式集合,並對每一個樣式進行置空刪除
- 若是不須要事件,則將其事件監聽的屬性去掉,即針對當前的節點取消事件代理:deleteListener(this, propKey)
- 若是舊屬性不在新屬性集合裏時,則須要刪除舊屬性:DOMPropertyOperations.deleteValueForProperty(getNode(this), propKey)
- 若是存在新樣式,則將新樣式進行合併。
- 若是在舊樣式中可是不在新樣式中,則清除該樣式.
- 若是既在舊樣式中也在新樣式中,且不相同,則更新該樣式styleUpdates[styleName] = nextProp[styleName]
- 若是在新樣式中,但不在舊樣式中,則直接更新爲新樣式styleUpdates = nextProp
- 若是存在事件更新,則添加事件監聽的屬性enqueuePutListener(this, propKey, nextProp, transaction)
- 若是存在新屬性,則添加新屬性, 或者更新舊的同名屬性DOMPropetyOperations.setValueForAttribute(node, propKey, nextProp)
//更新屬性
_updateDOMProperties: function(lastProps, nextProps, transaction) {
var propKey;
var styleName;
var styleUpdates;
//循環遍歷舊屬性,當舊屬性不在新屬性集合裏面的時候則要刪除
for (propKey in lastProps) {
//若是新屬性實例對象上有這個propKey 或者 propKey在舊屬性的原型上,則直接跳過,這樣剩下的都是不在
//新屬性集合裏面的,則都要刪除
if (nextProps.hasOwnProperty(propKey) ||
!lastProps.hasOwnProperty(propKey) ||
lastProps[propKey] == null) {
continue;
}
//刪除不須要的樣式
if (propKey === STYLE) {
var lastStyle = this._previousStyleCopy;
for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = '';
}
}
this._previousStyleCopy = null;
} else if (registrationNameModules.hasOwnProperty(propKey)) {
if (lastProps[propKey]) {
//這裏額事件監聽屬性須要去掉監聽,針對當前的節點取消事件代理。
deleteListener(this, propKey);
}
} else if (
DOMProperty.properties[propKey] ||
DOMProperty.isCustomAttribute(propKey)) {
//從DOM上刪除沒必要要的屬性
DOMPropertyOperations.deleteValueForProperty(getNode(this), propKey);
}
}
//針對新屬性,須要加到DOM節點上
for (propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp =
propKey === STYLE ? this._previousStyleCopy :
lastProps != null ? lastProps[propKey] : undefined;
//不在新屬性中,或者與舊屬性相同,則跳過
if (!nextProps.hasOwnProperty(propKey) ||
nextProp === lastProp ||
nextProp == null && lastProp == null) {
continue;
}
//DOM上寫入新樣式
if (propKey === STYLE) {
if (nextProp) {
if (__DEV__) {
checkAndWarnForMutatedStyle(
this._previousStyleCopy,
this._previousStyle,
this
);
this._previousStyle = nextProp;
}
nextProp = this._previousStyleCopy = Object.assign({}, nextProp);
} else {
this._previousStyleCopy = null;
}
if (lastProp) {
// Unset styles on `lastProp` but not on `nextProp`.
//在舊樣式中且不在新樣式中,且不相同,則更新該樣式
for (styleName in lastProp) {
if (lastProp.hasOwnProperty(styleName) &&
(!nextProp || !nextProp.hasOwnProperty(styleName))) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = '';
}
}
// Update styles that changed since `lastProp`.
for (styleName in nextProp) {
if (nextProp.hasOwnProperty(styleName) &&
lastProp[styleName] !== nextProp[styleName]) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = nextProp[styleName];
}
}
} else {
//不存在舊樣式,則直接寫入新樣式
// Relies on `updateStylesByID` not mutating `styleUpdates`.
styleUpdates = nextProp;
}
} else if (registrationNameModules.hasOwnProperty(propKey)) {
if (nextProp) {
//添加事件監聽屬性
enqueuePutListener(this, propKey, nextProp, transaction);
} else if (lastProp) {
//
deleteListener(this, propKey);
}
//添加新的屬性,或者更新舊的同名屬性
} else if (isCustomComponent(this._tag, nextProps)) {
if (!RESERVED_PROPS.hasOwnProperty(propKey)) {
DOMPropertyOperations.setValueForAttribute(
getNode(this),
propKey,
nextProp
);
}
} else if (
DOMProperty.properties[propKey] ||
DOMProperty.isCustomAttribute(propKey)) {
var node = getNode(this);
// If we're updating to null or undefined, we should remove the property // from the DOM node instead of inadvertently setting to a string. This // brings us in line with the same behavior we have on initial render. if (nextProp != null) { DOMPropertyOperations.setValueForProperty(node, propKey, nextProp); } else { //若是更新爲null或者undefined,則執行刪除屬性操做 DOMPropertyOperations.deleteValueForProperty(node, propKey); } } } //若是styleUpdates不爲空,則設置新樣式 if (styleUpdates) { CSSPropertyOperations.setValueForStyles( getNode(this), styleUpdates, this ); } }, 複製代碼
在mountComponent的時候,ReactComponent經過this.createContentMarkup處理DOM節點。 首先,獲取節點內容props.dangerourslySetInnerHTML,若是存在子節點,則經過this.mountChildren對子節點進行初始化渲染。dom
//ReactComponent.js
//_createContentMarkup
_createContentMarkup: function(transaction, props, context) {
var ret = '';
//獲取子節點渲染出內容
var innerHTML = props.dangerouslySetInnerHTML;
if (innerHTML != null) {
if (innerHTML.__html != null) {
ret = innerHTML.__html;
}
} else {
var contentToUse =
CONTENT_TYPES[typeof props.children] ? props.children : null;
var childrenToUse = contentToUse != null ? null : props.children;
if (contentToUse != null) {
// TODO: Validate that text is allowed as a child of this node
ret = escapeTextContentForBrowser(contentToUse);
} else if (childrenToUse != null) {
//對子節點進行初始化渲染
var mountImages = this.mountChildren(
childrenToUse,
transaction,
context
);
ret = mountImages.join('');
}
}
//是否須要換行
if (newlineEatingTags[this._tag] && ret.charAt(0) === '\n') {
return '\n' + ret;
} else {
return ret;
}
},
複製代碼
當receiveComponent,ReactComponet會經過updateDOMChildren來更新DOM內容和節點。ui
預覽地址 this