React 源碼學習(三):CSS 樣式及 DOM 屬性

閱讀源碼成了今年的學習目標之一,在選擇 Vue 和 React 之間,我想先閱讀 React 。 在考慮到讀哪一個版本的時候,我想先接觸到源碼早期的思想可能會更輕鬆一些,最終我選擇閱讀 0.3-stable 。 那麼接下來,我將從幾個方面來解讀這個版本的源碼。javascript

  1. React 源碼學習(一):HTML 元素渲染
  2. React 源碼學習(二):HTML 子元素渲染
  3. React 源碼學習(三):CSS 樣式及 DOM 屬性
  4. React 源碼學習(四):事務機制
  5. React 源碼學習(五):事件機制
  6. React 源碼學習(六):組件渲染
  7. React 源碼學習(七):生命週期
  8. React 源碼學習(八):組件更新

生成 Markup 標記時如何插入 CSS 樣式及 DOM 屬性

關於 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

先來看下這個 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);
  }
};
複製代碼

生成 CSS 樣式

校驗下 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;
  },
};
複製代碼

駝峯處理函數 hyphenate

// 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 樣式值處理函數

那麼到此, 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 Attribute 及 DOM Property 相關的操做對象

鍵和值處理完後,進行對應的拼接便可。接下來要作的就是建立 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;
}
複製代碼

生成 DOM Attribute 及 DOM Property 的 Markup 標記

// 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 屬性。

相關文章
相關標籤/搜索