React创建组件-创建DOM组件

一、创建DOM组件

React中Virtual DOM几乎涵盖了所有的原生DOM。React大部分工作都是在Virtual DOM完成的。 ReactDOMComponent针对Virturl DOM主要进行了一下处理:

  • 属性的操作,事件的处理
  • 子节点的更新

二、如何更新属性

当执行mountComponent时,ReactDOMComponent首先会生成标记和标签。通过createOpenTagMarkupAndPutListeners(transaction) 来处理DOM节点的属性和事件。

  • 如果存在事件,则对当前的节点添加事件代理,调用enqueuePutListener(this, propKey, propValue, transaction)
  • 如果存在样式,首先对样式合并Object.assign({}, prop.style),然后再调用CSSPropertyOperations.createMarkupForStyles(propValue, this) 创建样式。
  • 通过DOMPropertyOperations.createMarkupForProperty(propKey, propValue) 创建属性
  • 通过DOMPropertyOperations.crateMarkupForId(this._domID) 创建唯一标识。

源码如下:

//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节点属性。

  • 删除不需要的旧属性
  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
      );
    }
  },
复制代码

三、如何更新子节点

在mountComponent的时候,ReactComponent通过this.createContentMarkup处理DOM节点。 首先,获取节点内容props.dangerourslySetInnerHTML,如果存在子节点,则通过this.mountChildren对子节点进行初始化渲染。

//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内容和节点。

预览地址

转载于:https://juejin.im/post/5c85cc0ee51d45747554a699

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值