React建立組件-建立DOM組件

1、建立DOM組件

React中Virtual DOM幾乎涵蓋了全部的原生DOM。React大部分工做都是在Virtual DOM完成的。 ReactDOMComponent針對Virturl DOM主要進行了一下處理:html

  • 屬性的操做,事件的處理
  • 子節點的更新

2、如何更新屬性

當執行mountComponent時,ReactDOMComponent首先會生成標記和標籤。經過createOpenTagMarkupAndPutListeners(transaction) 來處理DOM節點的屬性和事件。node

  • 若是存在事件,則對當前的節點添加事件代理,調用enqueuePutListener(this, propKey, propValue, transaction)
  • 若是存在樣式,首先對樣式合併Object.assign({}, prop.style),而後再調用CSSPropertyOperations.createMarkupForStyles(propValue, this) 建立樣式。
  • 經過DOMPropertyOperations.createMarkupForProperty(propKey, propValue) 建立屬性
  • 經過DOMPropertyOperations.crateMarkupForId(this._domID) 建立惟一標識。

源碼以下: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

  • 刪除不須要的舊屬性
  1. 若是不須要舊樣式,則遍歷舊樣式集合,並對每一個樣式進行置空刪除
  2. 若是不須要事件,則將其事件監聽的屬性去掉,即針對當前的節點取消事件代理:deleteListener(this, propKey)
  3. 若是舊屬性不在新屬性集合裏時,則須要刪除舊屬性:DOMPropertyOperations.deleteValueForProperty(getNode(this), propKey)
  • 再更新屬性
  1. 若是存在新樣式,則將新樣式進行合併。
  2. 若是在舊樣式中可是不在新樣式中,則清除該樣式.
  3. 若是既在舊樣式中也在新樣式中,且不相同,則更新該樣式styleUpdates[styleName] = nextProp[styleName]
  4. 若是在新樣式中,但不在舊樣式中,則直接更新爲新樣式styleUpdates = nextProp
  5. 若是存在事件更新,則添加事件監聽的屬性enqueuePutListener(this, propKey, nextProp, transaction)
  6. 若是存在新屬性,則添加新屬性, 或者更新舊的同名屬性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 ); } }, 複製代碼

3、如何更新子節點

在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

相關文章
相關標籤/搜索