原生组件

原生组件(ReactNativeComponent)是React框架内部处理HTML元素的组件,它继承自ReactComponent。由于HTML元素并没有内部状态一说,只需要通过维护属性(props)即可。

构造函数

原生组件的初始化函数记录了对应的HTML元素标签信息,以方便在渲染时生成相应的HTML代码。HTML标签一般都是闭合的,当然也有例外,例如<br />,<embed />, <hr />等,因此,构造函数包含两个参数,第一个参数表示该元素的标签名称,第二个参数指示该标签是否可以自闭合。

function ReactNativeComponent(tag, omitClose) {
  this._tagOpen = '<' + tag + ' ';
  this._tagClose = omitClose ? '' : '</' + tag + '>';
  this.tagName = tag.toUpperCase();
}

组件挂载

原生组件的挂载过程相对比较简单,挂载时会先显式地调用ReactComponent.Mixin.mountComponent方法,前文提到过,Mixin中的同名方法会被覆盖,因此这里需要显式调用ReactComponent中定义的mountComponent方法以完成ref相关的工作。

随后,根据当前原生组件的属性信息,创建相应的HTML代码段,通过调用_createOpenTagMarkup、_createContentMarkup和_tagClose,依次生成开标签、内容代码段和闭合标签。_tagClose在构造函数阶段就被确定,而另外两个函数调用则依赖组件的属性信息。

属性校验

在生成HTML代码前,原生组件会校验当前的属性信息是否符合要求。校验的内容针对syle属性和子元素。属性中定义的style要么为空,要么应当以JSON对象的形式定义各个style信息,如果遇到包含连字符的style属性,统一转化成驼峰规则。例如z-index,在style对象中的键名为zIndex。

原生组件还有可能包含子节点或者内容信息。但属性中children、content只能出现其中之一,包含content的HTML元素不会有子节点;包含子节点的标签也不能设置content。除此之前,React内部还有一个dangerouslySetInnerHTML,它与children和content也是互斥的,这三个属性至多只允许出现其中之一。

创建开标签

在创建开标签时,关注点就在于如何根据属性生成HTML标签的props代码段。HTML的开标签中,可能会出现的内容有:
通用的HTML标签属性
某些标签特有的属性
事件处理函数
自定义数据
通用的HTML标签属性中,还有一个style属性,由于该属性相对复杂一些,React中将它单独提出进行了处理。

创建开标签的代码如下 :

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 {
        if (propKey === STYLE) {
          if (propValue) {
            propValue = props.style = merge(props.style);
          }
          propValue = CSSPropertyOperations.createMarkupForStyles(propValue);
        }
        var markup =
          DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
        if (markup) {
          ret += ' ' + markup;
        }
      }
}

创建内容标签

如果该组件包含属性dangerouslySetInnerHTML,则直接返回该该属性的内部值__html作为返回结果,代码如下所示
exit: ⌘ ↩
var innerHTML = this.props.dangerouslySetInnerHTML;
if (innerHTML != null) {
if (innerHTML.__html != null) {
return innerHTML.__html;
}
}

若不包含dangerouslySetInnerHTML,将优先查看是否存在props.content,如果存在,则将content内容执行escape之后返回。escape函数定义在src/utils/escapeTextForBrowser.js中。

在不是前两种情形的话,那么该组件的内容就是包含了其他子元素,这时则调用mountMultiChild来生成内容片段。关于mountMultiChild的细节会单独抽章节介绍。

属性更新

原生组件的属性有可能被更新,有可能更新的是HTML标签自身的属性,也有可能更新的是props中传递的children。涉及到原生组件自身的属性改变,通过调用_updateDOMProperties来实现。

当属性为style、dangerouslySetInnerHTML、content或者事件监听器时,走对应的处理流程。而一般的属性更新,则是通过ReactDOMIDOperation.updatePropertyByID来实现。为了加快查询速度,React会将getElementById的结果缓存在ReactDOMNodeCache中。相关代码如下:

 updatePropertyByID: function(id, name, value) {
    var node = ReactDOMNodeCache.getCachedNodeByID(id);
    invariant(
      !INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
      'updatePropertyByID(...): %s',
      INVALID_PROPERTY_ERRORS[name]
    );
    DOMPropertyOperations.setValueForProperty(node, name, value);
  }

真正执行属性更新的函数,是定义在src/domUtils/DOMPropertyOperations.js中的setValueForProperty,内部都是通过调用node.setAttribute或者node.removeAttribute接口完成。相关代码如下:

  setValueForProperty: function(node, name, value) {
    if (DOMProperty.isStandardName[name]) {
      var mutationMethod = DOMProperty.getMutationMethod[name];
      if (mutationMethod) {
        mutationMethod(node, value);
      } else if (DOMProperty.mustUseAttribute[name]) {
        if (DOMProperty.hasBooleanValue[name] && !value) {
          node.removeAttribute(DOMProperty.getAttributeName[name]);
        } else {
          node.setAttribute(DOMProperty.getAttributeName[name], value);
        }
      } else {
        var propName = DOMProperty.getPropertyName[name];
        if (!DOMProperty.hasSideEffects[name] || node[propName] !== value) {
          node[propName] = value;
        }
      }
    } else if (DOMProperty.isCustomAttribute(name)) {
      node.setAttribute(name, value);
    }
  }

组件卸载

原生组件执行卸载是,除了显式调用ReactComponent.Mixin.unmountComponent之外,还需要卸载其内部的子组件,并删相关的事件监听器。代码如下

unmountComponent: function() {
    ReactComponent.Mixin.unmountComponent.call(this);
    this.unmountMultiChild();
    ReactEvent.deleteAllListeners(this._rootNodeID);
  }
Show Comments

Get the latest posts delivered right to your inbox.