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

相关文章
相关标签/搜索