React-源碼解析-DOM模型

image

JSX解析

1.jsx建立的虛擬元素會被編譯成React的createElement方法

var ReactElement = function(type, key, ref, self, source, owner, props) {
  var element = {
    // 這個標籤容許咱們將其做爲一個反應元素進行惟一的識別
    $$typeof: REACT_ELEMENT_TYPE,

    // 屬於元素的內置屬性
    type: type,
    key: key,
    ref: ref,
    props: props,

    // 記錄負責建立該元素的組件
    _owner: owner,
  };
  return element;
};

ReactElement.createElement = function(type, config, children) {
  //初始化參數
  var propName;
  var props = {};
  var key = null;
  var ref = null;
  var self = null;
  var source = null;
  //若是存在cinfig就提取裏面的內容
  if (config != null) {
    //若是存在ref,key就賦值屬性
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    //複製config裏的內容到props中,除了上面賦值的這些屬性
    for (propName in config) {
      if (hasOwnProperty.call(config, propName) &&!RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }
  //處理children,所有掛載到props的children屬性上,若是隻有一個參數,直接賦值給children,不然合併處理
  var childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    var childArray = Array(childrenLength);
    for (var i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;//合併成名爲childArray的數組
  }
  //若是某個prop爲空且存在默認的prop,則將默認prop賦給當前的prop
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  //返回一個ReactElement實例對象
  return ReactElement( type,  key, ref, self, source, ReactCurrentOwner.current, props, );
};
複製代碼

2.建立組件

使用React建立組件,會先調用instantiateReactComponent, 這是初始化組件的入口,經過判斷參數node的類型來區分建立什麼類型的組件

  1. 當node爲空時,說明node不存在,則初始化一個空的組件. ReactEmptyComponent.create(instantiateReactComponent)。
  2. 當node類型爲對象時,那麼就是DOM標籤組件或者說是自定義組件, 若是element類型(element = node,element.type='string')爲字符串,就會初始化一個dom標籤組件:ReactNativeComponent.createInternalComponent(element), 不然就會初始化一個自定義組件 ReactCompositeComponentWrapper()
  3. 當node類型爲字符串或者數字時,則初始化文本組件,ReactNativeComponent.createInstanceForText(node)。
  4. 其他狀況不作處理
//初始化組件的入口:已刪除不在主線路線的代碼
function instantiateReactComponent(node, shouldHaveDebugID) {
  var instance;
  if (node === null || node === false) {
    //node爲空的時候建立一個空組件
    instance = ReactEmptyComponent.create(instantiateReactComponent);
  } else if (typeof node === 'object') {
    var element = node;
    // node爲object而且element的type爲string,建立dom組件
    if (typeof element.type === 'string') {
      instance = ReactHostComponent.createInternalComponent(element);
    } else if (isInternalComponentType(element.type)) {
      //不處理:不是字符串表示的自定義組件暫時沒法使用,此處不作組件初始化操做
      instance = new element.type(element);
    } else {
      //建立自定義組件
      instance = new ReactCompositeComponentWrapper(element);
    }
  } else if (typeof node === 'string' || typeof node === 'number') {
    //建立文本組件
    instance = ReactHostComponent.createInstanceForText(node);
  } else {
    //不作處理
  }
  // 這兩個字段被DOM和ART擴散算法使用
  instance._mountIndex = 0;
  instance._mountImage = null;
  return instance;
}
複製代碼

2.1文本組件

  1. 根據transaction.useCreateElement判斷文本是否是經過createElement建立的節點
  2. 是,則爲這個節點建立標籤和標識domID就能夠參與虛擬dom的diff
  3. 若是不是就直接返回文本
var ReactDOMTextComponent = function(text) {
  //保存當前字符串
  this._currentElement = text;
  this._stringText = '' + text;
  //ReactDOMComponentTree時須要的參數
  this._hostNode = null;
  this._hostParent = null;
  //屬性
  this._domID = 0;
  this._mountIndex = 0;
  this._closingComment = null;
  this._commentNodes = null;
};

Object.assign(ReactDOMTextComponent.prototype, {
  mountComponent: function(transaction, hostParent, hostContainerInfo, context, ) {
    var domID = hostContainerInfo._idCounter++;
    var openingValue = ' react-text: ' + domID + ' ';
    var closingValue = ' /react-text ';
    this._domID = domID;
    this._hostParent = hostParent;
    //根據transaction.useCreateElement判斷文本是不是經過createElement方法建立的節點
    if (transaction.useCreateElement) {
      //若是是createElement建立的節點,爲這個節點建立相應的標籤和標識domID,這樣就跟別的React節點同樣,能夠參與虛擬dom的diff權利
      var ownerDocument = hostContainerInfo._ownerDocument;
      var openingComment = ownerDocument.createComment(openingValue);
      var closingComment = ownerDocument.createComment(closingValue);
      var lazyTree = DOMLazyTree(ownerDocument.createDocumentFragment());
      //開始標籤
      DOMLazyTree.queueChild(lazyTree, DOMLazyTree(openingComment));
      //若是是文本類型,則建立文本節點
      if (this._stringText) {
        DOMLazyTree.queueChild(
          lazyTree,
          DOMLazyTree(ownerDocument.createTextNode(this._stringText)),
        );
      }
      //結束標籤
      DOMLazyTree.queueChild(lazyTree, DOMLazyTree(closingComment));
      ReactDOMComponentTree.precacheNode(this, openingComment);
      this._closingComment = closingComment;
      return lazyTree;
    } else {
      //若是不是經過createElemet建立的節點,則直接返回文本
      var escapedText = escapeTextContentForBrowser(this._stringText);//boolean number轉爲字符串
      //靜態頁面下直接返回文本
      if (transaction.renderToStaticMarkup) {
        return escapedText;
      }
      //若是不是經過createElement建立的文本,則將標籤和屬性註釋掉,直接返回文本內容
      return ( '<!--' + openingValue + '-->' + escapedText + '<!--' + closingValue + '-->' );
    }
  },

  //更新文本內容
  receiveComponent: function(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,
        );
      }
    }
  },
});
複製代碼

2.2 DOM標籤組件

  1. 虛擬dom涵蓋了原生的DOM標籤,使用div不是原div,他是一個虛擬dom對象,標籤名相同
  2. 屬性更新:更新樣式,更新屬性,處理事件
  3. 子節點更新:更新內容,更新子節點,

屬性更新

/** * 在mountComponent中經過這個方法處理DOM節點的屬性和時間 * 1 若是存在事件,則針對當前的節點添加事件代理,既調用enqueuePutListener(this, propKey, propValue, transaction); * 2 若是存在樣式,首先會對樣式進行合併,Object.assign({},props.style,);而後經過CSSPropertyOperations.createMarkupForStyles( propValue,this,)建立樣式 * 3 經過DOMPropertyOperations.createMarkupForProperty(propKey,propValue,);建立屬性 * 4 經過DOMPropertyOperations.createMarkupForID(this._domID)建立惟一標識 */
  _createOpenTagMarkupAndPutListeners: function(transaction, props) {
    var ret = '<' + this._currentElement.type;
    //for in + hasOwnProperty組合,for in會迭代原型鏈,hasOwnProperty只是該對象
    //迭代屬性,若是屬性不在該props上執行下次循環
    for (var propKey in props) {
      if (!props.hasOwnProperty(propKey)) {
        continue;
      }
      var propValue = props[propKey];
      if (propValue == null) {
        continue;//取出來的屬性爲null,執行下次循環
      }
      if (registrationNameModules.hasOwnProperty(propKey)) {//????應該是事件
        if (propValue) {
          //添加事件代理
          enqueuePutListener(this, propKey, propValue, transaction);
        }
      } else {
        if (propKey === STYLE) {
          if (propValue) {
            //若是有樣式,合併樣式
            propValue = this._previousStyleCopy = Object.assign({},props.style,);
          }
          //建立樣式
          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;
        }
      }
    }
    //靜態頁面,不設置react-id
    if (transaction.renderToStaticMarkup) {
      return ret;
    }
    //設置react-id
    if (!this._hostParent) {
      ret += ' ' + DOMPropertyOperations.createMarkupForRoot();
    }
    ret += ' ' + DOMPropertyOperations.createMarkupForID(this._domID);
    return ret;
  },

複製代碼
當執行receiveComponent方法時,經過this.updateComponent()更新DOM節點屬性
receiveComponent: function(nextElement, transaction, context) {
    var prevElement = this._currentElement;
    this._currentElement = nextElement;
    //更新DOM節點屬性
    this.updateComponent(transaction, prevElement, nextElement, context);
  }
複製代碼
在updateComponent中經過this._updateDOMProperties(lastProps, nextProps, transaction)更新DOM節點屬性
/** * 先是刪除不須要的舊屬性 * 若是不須要舊樣式,則遍歷舊樣式集合,並對每一個樣式進行置空操做 * 若是不須要事件,則將其的事件監聽去掉,針對當前節點取消事件代理deleteListener(this, propKey) * 若是舊屬性不在新屬性集合中,則須要刪除舊屬性DOMPropertyOperations.deleteValueForProperty(getNode(this), propKey); * 而後更新新屬性 * 若是存在新樣式,則將新樣式進行合併Object.assign({}, nextProp) * 若是在舊樣式中但不在新樣式中,則清除該樣式 * 若是既在舊樣式中也在新樣式中,且不相同,則更新該樣式styleUpdates[styleName] = nextProp[styleName]; * 若是在新樣式中,但不在舊樣式中,則直接更新爲新樣式styleUpdates = nextProp; * 若是存在事件更新,則添加事件監聽的屬性enqueuePutListener(this, propKey, nextProp, transaction); * 若是存在新屬性,則添加新屬性,或者更新舊的同名屬性DOMPropertyOperations.setValueForAttribute( getNode(this), propKey, nextProp, ); * */
  _updateDOMProperties: function(lastProps, nextProps, transaction) {
    var propKey;
    var styleName;
    var styleUpdates;
    for (propKey in lastProps) {
      //舊屬性不在新屬性集合中
      //新屬性集合中包括舊屬性or舊屬性在舊屬性原型鏈上or舊屬性爲null,執行下次循環,至關於刪除了這個屬性
      if ( nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null ) {
        continue;
      }
      //從dom上刪除不須要的樣式??? TODO 這不是刪除了全部樣式嗎
      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 (isCustomComponent(this._tag, lastProps)) {
        if (!RESERVED_PROPS.hasOwnProperty(propKey)) {
          DOMPropertyOperations.deleteValueForAttribute(getNode(this), propKey);
        }
      } else if (DOMProperty.properties[propKey] ||DOMProperty.isCustomAttribute(propKey)) {
        //刪除不須要的屬性
        DOMPropertyOperations.deleteValueForProperty(getNode(this), propKey);
      }
    }
    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;
      }
      if (propKey === STYLE) {
        if (nextProp) {
          //合併樣式
          nextProp = this._previousStyleCopy = Object.assign({}, nextProp);
        } else {
          this._previousStyleCopy = null;
        }
        if (lastProp) {
          //在舊樣式中不在新樣式中,清除該樣式
          for (styleName in lastProp) {
            if (lastProp.hasOwnProperty(styleName) &&(!nextProp || !nextProp.hasOwnProperty(styleName))) {
              styleUpdates = styleUpdates || {};
              styleUpdates[styleName] = '';
            }
          }
          // 既在舊樣式中,也在新樣式中,且不相同,則更新該樣式
          for (styleName in nextProp) {
            if (nextProp.hasOwnProperty(styleName) &&lastProp[styleName] !== nextProp[styleName]) {
              styleUpdates = styleUpdates || {};
              styleUpdates[styleName] = nextProp[styleName];
            }
          }
        } else {
          // 不存在舊樣式,直接寫入新樣式
          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 (nextProp != null) {
          DOMPropertyOperations.setValueForProperty(node, propKey, nextProp);
        } else {
          //若是更新爲null或者undefined 則執行刪除屬性操做
          DOMPropertyOperations.deleteValueForProperty(node, propKey);
        }
      }
    }
    //若是styleUpdates不爲空,則設置新樣式
    if (styleUpdates) {
      CSSPropertyOperations.setValueForStyles(getNode(this),styleUpdates,this,);
    }
  },

複製代碼

更新子節點

在執行mountComponent方法時,經過this._createContentMarkup(transaction, props, context);處理DOM節點的內容

_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方法時,經過this._updateDOMChildren(lastProps, nextProps, transaction, context);更新DOM內容和子節點

/** * 先是刪除不須要的子節點和內容 * 若是存在舊節點,而新節點不存在,說明當前節點在更新以後刪除,執行this.updateChildren(null, transaction, context); * 若是舊的內容存在,而新的內容不存在,說明當前內容在更新後被刪除,此時執行 this.updateTextContent(''); * 更新子節點和內容 * 若是新子節點存在,則更新其子節點,此時執行this.updateChildren(nextChildren, transaction, context); * 若是新的內容存在,則更新內容,此時執行方法this.updateTextContent('' + nextContent); * */
  _updateDOMChildren: function(lastProps, nextProps, transaction, context) {
    //初始化
    var lastContent = CONTENT_TYPES[typeof lastProps.children]  ? lastProps.children : null;
    var nextContent = CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;
    var lastHtml = lastProps.dangerouslySetInnerHTML && lastProps.dangerouslySetInnerHTML.__html;
    var nextHtml = nextProps.dangerouslySetInnerHTML && nextProps.dangerouslySetInnerHTML.__html;

    var lastChildren = lastContent != null ? null : lastProps.children;
    var nextChildren = nextContent != null ? null : nextProps.children;

    var lastHasContentOrHtml = lastContent != null || lastHtml != null;
    var nextHasContentOrHtml = nextContent != null || nextHtml != null;
    if (lastChildren != null && nextChildren == null) {
      //舊節點存在,而新節點不存愛,說明當前節點在更新後被刪除了
      this.updateChildren(null, transaction, context);
    } else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
      //說明當前內容在更新後被刪除了
      this.updateTextContent('');
    }
    //新節點存在
    if (nextContent != null) {
      if (lastContent !== nextContent) {
        //更新內容
        this.updateTextContent('' + nextContent);
      }
    } else if (nextHtml != null) {
      //更新屬性標識
      if (lastHtml !== nextHtml) {
        this.updateMarkup('' + nextHtml);
      }
    } else if (nextChildren != null) {
      //更新子節點
      this.updateChildren(nextChildren, transaction, context);
    }
  },
複製代碼
相關文章
相關標籤/搜索