閱讀源碼成了今年的學習目標之一,在選擇 Vue 和 React 之間,我想先閱讀 React 。 在考慮到讀哪一個版本的時候,我想先接觸到源碼早期的思想可能會更輕鬆一些,最終我選擇閱讀
0.3-stable
。 那麼接下來,我將從幾個方面來解讀這個版本的源碼。javascript
關於 CSS 樣式及 DOM 屬性,在以前有提到過在這裏實現,而且把代碼隱藏了,那麼此次咱們來進行解讀:html
// core/ReactNativeComponent.js
var STYLE = keyOf({style: null});
ReactNativeComponent.Mixin = {
_createOpenTagMarkup: function() {
var props = this.props;
var ret = this._tagOpen;
for (var propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
var propValue = props[propKey];
if (propValue == null) {
continue;
}
// 註冊事件相關,本次不作解讀
if (registrationNames[propKey]) {
putListener(this._rootNodeID, propKey, propValue);
} else {
// CSS 樣式處理
if (propKey === STYLE) {
if (propValue) {
propValue = props.style = merge(props.style);
}
propValue = CSSPropertyOperations.createMarkupForStyles(propValue);
}
// 建立 DOM 屬性 markup 標記
var markup =
DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
if (markup) {
// 拼接
ret += ' ' + markup;
}
}
}
return ret + ' id="' + this._rootNodeID + '">';
},
}
複製代碼
// vendor/core/keyOf.js
// 好比上面的 STYLE 返回的就是 'style'
var keyOf = function(oneKeyObj) {
var key;
for (key in oneKeyObj) {
if (!oneKeyObj.hasOwnProperty(key)) {
continue;
}
return key;
}
return null;
};
複製代碼
下面咱們依次來解讀 CSS 樣式 和 DOM 屬性。java
先來看下這個 merge
方法:node
// utils/merge.js
var merge = function(one, two) {
var result = {};
mergeInto(result, one);
mergeInto(result, two);
return result;
};
複製代碼
這裏須要注意, mergeInto
函數會對 one
, two
參數進行校驗。ajax
校驗他們是否爲 object
,而且不是 array
。緩存
// utils/mergeInto.js
function mergeInto(one, two) {
checkMergeObjectArg(one);
if (two != null) {
checkMergeObjectArg(two);
for (var key in two) {
if (!two.hasOwnProperty(key)) {
continue;
}
one[key] = two[key];
}
}
}
複製代碼
// utils/mergeHelpers.js
var isTerminal = function(o) {
return typeof o !== 'object' || o === null;
};
var mergeHelpers = {
checkMergeObjectArgs: function(one, two) {
mergeHelpers.checkMergeObjectArg(one);
mergeHelpers.checkMergeObjectArg(two);
},
checkMergeObjectArg: function(arg) {
throwIf(isTerminal(arg) || Array.isArray(arg), ERRORS.MERGE_CORE_FAILURE);
}
};
複製代碼
校驗下 props.style
傳入的是不是對象,而後建立 CSS markup 標記。咱們來看到 CSSPropertyOperations.createMarkupForStyles
方法:dom
// domUtils/CSSPropertyOperations.js
var processStyleName = memoizeStringOnly(function(styleName) {
return escapeTextForBrowser(hyphenate(styleName));
});
var CSSPropertyOperations = {
createMarkupForStyles: function(styles) {
var serialized = '';
// 遍歷 styles
for (var styleName in styles) {
if (!styles.hasOwnProperty(styleName)) {
continue;
}
var styleValue = styles[styleName];
if (typeof styleValue !== 'undefined') {
// 按照樣式名和樣式值組合拼接
serialized += processStyleName(styleName) + ':';
serialized += dangerousStyleValue(styleName, styleValue) + ';';
}
}
return serialized;
},
};
複製代碼
// vendor/core/hyphenate.js
// 駝峯轉爲「-」的形式,如:
// > hyphenate('backgroundColor')
// < "background-color"
var _uppercasePattern = /([A-Z])/g;
function hyphenate(string) {
return string.replace(_uppercasePattern, '-$1').toLowerCase();
}
複製代碼
在聲明 processStyleName
賦值後, memoizeStringOnly
就已經建立了一個 cache
用來緩存被駝峯轉換過的值,若存在則直接被取出。ide
// utils/memoizeStringOnly.js
function memoizeStringOnly(callback) {
var cache = {};
return function(string) {
if (cache.hasOwnProperty(string)) {
return cache[string];
} else {
return cache[string] = callback.call(this, string);
}
};
}
複製代碼
那麼到此, CSS 樣式的鍵已經實現完成,接下來看看他的值是如何進行操做的,接下來看到 dangerousStyleValue
:函數
// domUtils/dangerousStyleValue.js
function dangerousStyleValue(styleName, value) {
if (value === null || value === false || value === true || value === '') {
return '';
}
// value 不是數字的狀況
if (isNaN(value)) {
return !value ? '' : '' + value;
}
// 知足 isNumber 的狀況下,返回 value 自己,不然返回 + px
return CSSProperty.isNumber[styleName] ? '' + value : (value + 'px');
}
複製代碼
// domUtils/CSSProperty.js
var isNumber = {
fillOpacity: true,
fontWeight: true,
opacity: true,
orphans: true,
textDecoration: true,
zIndex: true,
zoom: true
};
var CSSProperty = {
isNumber: isNumber
};
複製代碼
鍵和值處理完後,進行對應的拼接便可。接下來要作的就是建立 DOM 屬性的 markup 標記。咱們來看到 DOMPropertyOperations.createMarkupForProperty
方法:學習
由於 DOMPropertyOperations
中涉及到不少 DOMProperty
的方法,因此這裏先解讀 DOMProperty
。
// domUtils/DOMProperty.js
var DOMProperty = {
// 檢查屬性名稱是否爲標準屬性。
isStandardName: {},
// 從規範化名稱映射到不一樣的屬性名稱。 在渲染標記或使用 `*Attribute()` 時使用屬性名稱。
getAttributeName: {},
// 從規範化名稱映射到DOM節點實例上的屬性。(這包括因外部因素而發生變異的屬性。)
getPropertyName: {},
// 從規範化名稱映射到變異方法。 只有在不能經過屬性或 `setAttribute()` 簡單地設置變異時,纔會存在這種狀況。
getMutationMethod: {},
// 是否必須訪問屬性並將其變爲對象屬性。
mustUseAttribute: {},
// 是否必須使用 `*Attribute()` 訪問和變異屬性。 (這包括 `<propName> 中的 <element>` 失敗的任何內容。)
mustUseProperty: {},
// 設置爲falsey值時是否應刪除該屬性。
hasBooleanValue: {},
// 是否設置值會致使反作用,例如觸發資源加載或文本選擇更改。 咱們必須確保只有在更改後才設置該值。
hasSideEffects: {},
// 檢查屬性名稱是否爲自定義屬性。
isCustomAttribute: RegExp.prototype.test.bind(
/^(data|aria)-[a-z_][a-z\d_.\-]*$/
)
};
// 從規範化的 camelcased 屬性名稱映射到指定應如何訪問或呈現關聯DOM屬性的配置。
var MustUseAttribute = 0x1;
var MustUseProperty = 0x2;
var HasBooleanValue = 0x4;
var HasSideEffects = 0x8;
var Properties = {
// 標準屬性
accept: null,
ajaxify: MustUseAttribute,
allowFullScreen: MustUseAttribute | HasBooleanValue,
autoplay: HasBooleanValue,
checked: MustUseProperty | HasBooleanValue,
className: MustUseProperty,
value: MustUseProperty | HasSideEffects,
// ...
};
// 未指定的屬性名稱使用 **小寫** 規範化名稱。
var DOMAttributeNames = {
className: 'class',
htmlFor: 'for',
// ...
};
// 未指定的屬性名稱使用規範化名稱。
var DOMPropertyNames = {
autoComplete: 'autocomplete',
spellCheck: 'spellcheck'
};
// 須要特殊變異方法的屬性。
var DOMMutationMethods = {
className: function(node, value) {
node.className = value || '';
}
};
// 遍歷標準屬性
for (var propName in Properties) {
// isStandardName 對應的標準屬性設置爲 true
DOMProperty.isStandardName[propName] = true;
// 標準屬性的映射,對應 DOMAttributeNames 進行操做
DOMProperty.getAttributeName[propName] =
DOMAttributeNames[propName] || propName.toLowerCase();
// 一樣的,標準屬性的映射,對應 DOMPropertyNames 進行操做
DOMProperty.getPropertyName[propName] =
DOMPropertyNames[propName] || propName;
// 檢查特殊編譯方法是否存在
var mutationMethod = DOMMutationMethods[propName];
if (mutationMethod) {
// 賦值對應方法
DOMProperty.getMutationMethod[propName] = mutationMethod;
}
// 獲取標準屬性的值
var propConfig = Properties[propName];
// 必須使用屬性
DOMProperty.mustUseAttribute[propName] = propConfig & MustUseAttribute;
DOMProperty.mustUseProperty[propName] = propConfig & MustUseProperty;
// 具備布爾值
DOMProperty.hasBooleanValue[propName] = propConfig & HasBooleanValue;
// 具備反作用
DOMProperty.hasSideEffects[propName] = propConfig & HasSideEffects;
}
複製代碼
// domUtils/DOMPropertyOperations.js
// 一樣這裏也用到了 DOM 屬性「鍵」的緩存
var processAttributeNameAndPrefix = memoizeStringOnly(function(name) {
return escapeTextForBrowser(name) + '="';
});
var DOMPropertyOperations = {
createMarkupForProperty: function(name, value) {
// 首先檢查 name 是否爲標準屬性
if (DOMProperty.isStandardName[name]) {
// 判斷 name 是否具備布爾值,而且 value 經過隱式轉換爲 false 的狀況
if (value == null || DOMProperty.hasBooleanValue[name] && !value) {
return '';
}
// 獲取屬性映射的對應 attributeName
var attributeName = DOMProperty.getAttributeName[name];
// 進行 markup 拼接處理
return processAttributeNameAndPrefix(attributeName) +
escapeTextForBrowser(value) + '"';
// 若 name 爲自定義樣式,如: data- 這樣的
} else if (DOMProperty.isCustomAttribute(name)) {
if (value == null) {
return '';
}
// 一樣的 markup 拼接處理
return processAttributeNameAndPrefix(name) +
escapeTextForBrowser(value) + '"';
} else {
// 其餘狀況
return null;
}
}
};
複製代碼
那麼到此,實現 CSS 樣式及 DOM 屬性。