Element 为Object:type为string

394 阅读9分钟

当,element的type为 string。比如上边传送门,当然node为null的时候,初始化了 一个ReactEmptyComponent 实例,而后赋值的时候。 this._renderedComponent = instantiate(placeholderElement); placeholderElement 是一个element,而且该element.type 是一个字符串,值为 'noscript'

另外的,element.type === 'string' 的情况是 element.type 为 'div'等HTML标签。

ReactNativeComponent 模块

在 Element的 type为 string的时候的情况如下所示,主要是 调用了 ReactNativeComponent模块的 createInternalComponent 方法来生成挂载实例。

    if (typeof element.type === 'string') {
            '
            ReactNativeComponent.createInternalComponent 方法是被注入进来的,注入的是 ReactDOMComponent 类。
            最终的结构是 new ReactDOMComponent(element.type,element.props)
    
            生成一个 ReactDOMComponent 的实例返回
            '
          instance = ReactNativeComponent.createInternalComponent(element);--------(2)
    }

我们来看看React如何将 element.type === 'string' 的 element 初始化为要挂载的实例。

createInternalComponent

createInternalComponent作用是返回一个 genericComponentClass 实例。

    function createInternalComponent(element) {
        return new genericComponentClass(element.type, element.props);
    }

ReactDOMComponent

genericComponentClass 是注入进来的 ReactDOMComponent模块。 所以,new genericComponentClass() 就等价于 new ReactDOMComponent()

ReactDOMComponent 模块是React组件的核心,ReactDOMComponent会将element.type为div的element转换为挂载实例。

一般我们绑定的事件都是绑定在这些组件上的,所以Element 为Object并且type为string是React组件的基础,即使是我们自定义的组件最终也是由这些组件组成的。这一点,在 传送门一文中有表述。

综合的来看 ReactDOMComponent 模块与 ReactDOMTextComponent 模块一样,只是所包含的内容更多。

ReactDOMComponent 构造函数

    function ReactDOMComponent(tag) {
      validateDangerousTag(tag);
      this._tag = tag.toLowerCase();
      this._renderedChildren = null;
      this._previousStyle = null;
      this._previousStyleCopy = null;
      this._rootNodeID = null;
      this._wrapperState = null;
      this._topLevelWrapper = null;
      this._nodeWithLegacyProperties = null
    }
    

这个构造函数很简单,接收一个参数tag,也就是 element.type,而后初始化一些属性。

ReactDOMComponent的核心是原型属性。

construct

只是将当前的element赋值给 _currentElement。

    construct: function (element) {
        this._currentElement = element;
    },

mountComponent 1

方法的作用也是很简单,就是生成一个 mountImage。 这里React根据 不同的html标签选择不同的方式来处理。

    mountComponent:function(){
        '
            赋值 _rootNodeID
        '
        this._rootNodeID = rootID;
        
        '
            拿到 props
        '
        var props = this._currentElement.props;
        
        '
            判断标签,选择不同的处理方式。我们省略别的,以textarea为例子。
        '
        switch (this._tag) {
            ...
            
            case 'textarea':
                '
                    主要的作用是拿到 props,我们看看它是如何处理的。
                '
                ReactDOMTextarea.mountWrapper(this, props, context);
                props = ReactDOMTextarea.getNativeProps(this, props, context);
                break;
        }
        
       
    }
ReactDOMTextarea.mountWrapper

参数:

this: 当前组件

props:当前组件的props属性

context:组件所处的位置信息。

    mountWrapper: function (inst, props) {
        '
            先看一下给的有没有 defaultValue。这里defaultValue 就是我们定义textarea定义的默认值。
        '
        var defaultValue = props.defaultValue;
        
        '
            获得props的children属性。
            这里再textarea标签内写入的内容便是 children,实际上这个children与defaultValue是差不多的东西。这里React建议使用 value或者是defaultValue来代替 children
        '
        var children = props.children;
        
        if (children != null) {

          if (Array.isArray(children)) {
           
            children = children[0];
          }
    
          defaultValue = '' + children;
        }
        if (defaultValue == null) {
          defaultValue = '';
        }
        
        '
            value,便是我们绑定的value值。
            比如
            <textarea
              cols="30"
              rows="10"
              defaultValue="123"
              value={this.state.text}
            ></textarea>
        '
        var value = LinkedValueUtils.getValue(props);
        
        '
            这里 inst 便是组件 textarea本身。这里是为这个 textarea加入了一个属性_wrapperState。顾名思义,这个属性就是对 state的一个包装,包装中有俩属性,一个是 值,一个是方法,change方法。
            关于整个inst,如下图所示。
            
            _handleChange方法也如下所示。
        '
        inst._wrapperState = {
          initialValue: '' + (value != null ? value : defaultValue),
          onChange: _handleChange.bind(inst)
        };
      },

ReactDOMTextarea.getNativeProps

主要的作用是定义一些属性,这些属性是 textarea所依赖的。这些属性主要是留待后用。

    getNativeProps: function (inst, props, context) {
       
        var nativeProps = assign({}, props, {
          defaultValue: undefined,
          value: undefined,
          children: inst._wrapperState.initialValue,
          onChange: inst._wrapperState.onChange
        });
    
        return nativeProps;
      },

mountComponent 2

    mountComponent:function (rootID, transaction, context) {
        '
            useCreateElement为false
        '
        if (transaction.useCreateElement) {
            var ownerDocument = context[ReactMount.ownerDocumentContextKey];
            var el = ownerDocument.createElement(this._currentElement.type);
            DOMPropertyOperations.setAttributeForID(el, this._rootNodeID);
            
              ReactMount.getID(el);
              this._updateDOMProperties({}, props, transaction, el);
              this._createInitialChildren(transaction, props, context, el);
              mountImage = el;
            } 
            else 
            {
            '
                创建 tagOpen,也就是 html标签的左半边,里边会包含各种各样的属性。比如我们以上边的 textarea为例,其tagOpen就如下:
                <textarea name="" id="" cols="30" rows="10" data-reactid=".0.0.3"
            '
              var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props);
              '
                这就是创建 标签包裹的内容。
              '
              var tagContent = this._createContentMarkup(transaction, props, context);
              
              '
                这里就是将 tagOpen 和 tagContent 和 '/>'组合起来生成一个 markup。
              '
              if (!tagContent && omittedCloseTags[this._tag]) {
                mountImage = tagOpen + '/>';
              } else {
                mountImage = tagOpen + '>' + tagContent + '</' + this._currentElement.type + '>';
          }
        }
    }
_createOpenTagMarkupAndPutListeners

简单的来说,_createOpenTagMarkupAndPutListeners方法的作用就是将 props中的属性拿出来,根据 element.type也就是 this._tag的值,来创建一个tagOpen,也就是 html标签的左侧开口部分。这部分里我们前边说过了。

另一方面,在_createOpenTagMarkupAndPutListeners里,React会接触到我们定义在组件上的事件,click,change等的事件,React会在这里接收这些事件,而后进行处理。

这里就衔接上了我们之前说的 关于 React的事件机制的那一部分内容。

关于 _createOpenTagMarkupAndPutListeners 方法的源码我们就不再贴了,简单的来说,就根据 props的不同。进行分类的处理,比如说 事件,比如说样式等等。

最终就是生产一个 tagOpen。

场中总结:

这里的 mountComponent 方法可以说是组件最为重要的方法。其作用有二:

一是:mountComponent 方法会根据 组件的属性(props)来生成 markup

二是:mountComponent 方法会根据 props的类型来选择不同的处理方式。

receiveComponent

receiveComponent 方法也是挂载实例的方法,代码很简单。

一是: 保存一下当前的组件

二是:将 要更新的新组件 赋值给 _currentElement

三是:调用 updateComponent 方法来更新

    receiveComponent: function (nextElement, transaction, context) {
        var prevElement = this._currentElement;
        this._currentElement = nextElement;
        this.updateComponent(transaction, prevElement, nextElement, context);
    },
    

updateComponent

    updateComponent: function (transaction, prevElement, nextElement, context) {
        var lastProps = prevElement.props;
        var nextProps = this._currentElement.props;
    
        switch (this._tag) {
          ...
          case 'textarea':
            '
                我们依旧以 textarea 为例。
                mountComponent方法这里用的是 mountWrapper方法,这里当然是 updateWrapper方法了
                updateWrapper方法往下看。
            '
            ReactDOMTextarea.updateWrapper(this);
            
            '
                处理一下 props
            '
            lastProps = ReactDOMTextarea.getNativeProps(this, lastProps);
            
            nextProps = ReactDOMTextarea.getNativeProps(this, nextProps);
            break;
        }
    
       
        '
            assertValidProps 就是判断 props的。
        '
        assertValidProps(this, nextProps);
        
        '
            根据lastProps和nextProps来更新 标签的属性。
        '
        this._updateDOMProperties(lastProps, nextProps, transaction, null);
        this._updateDOMChildren(lastProps, nextProps, transaction, context);
    
        if (!canDefineProperty && this._nodeWithLegacyProperties) {
          this._nodeWithLegacyProperties.props = nextProps;
        }
    
      },
updateWrapper
    updateWrapper: function (inst) {
        '
            拿到 组件的 props属性
        '
        var props = inst._currentElement.props;
        '
            拿到 value值。也就是 textarea身上绑定的值。
        '
        var value = LinkedValueUtils.getValue(props);
        '
            如果 value有值
        '
        if (value != null) {
         
          ReactDOMIDOperations.updatePropertyByID(inst._rootNodeID, 'value', '' + value);
        }
    }
ReactDOMIDOperations.updatePropertyByID

顾名思义就是 根据id来更新组件的属性。也就是使用setAttribute方法来给html标签来添加属性。样式,value之类的。

    updatePropertyByID: function (id, name, value) {
        '
            根据id拿到node
        '
        var node = ReactMount.getNode(id);
        
        '
            setValueForProperty方法是核心,就是setAttribute的使用,为textarea的value属性赋值。
        '
        if (value != null) {
          DOMPropertyOperations.setValueForProperty(node, name, value);
        } else {
          DOMPropertyOperations.deleteValueForProperty(node, name);
        }
    },
_updateDOMProperties

简单的来说就是根据,新的属性和值与 旧的属性和值的差异,根据需要来更新这属性。 比如:

    <textarea
      style={style}
      className="textarea"
      name=""
      id=""
      cols="30"
      rows="10"
      defaultValue="123"
      value={this.state.text}
      onChange={this.handleClick}
    ></textarea>

如上的例子所示,一个textarea的属性的props便如下所示。

```
    style: {color: "pink"}
    className: "textarea"
    name: ""
    id: ""
    cols: "30"
    rows: "10"
    defaultValue: undefined
    value: undefined
    onChange: ƒ ()
    children: "this is text node"
    __proto__: Object
```

代码有点长。

    _updateDOMProperties: function (lastProps, nextProps, transaction, node) {
        var propKey;
        var styleName;
        var styleUpdates;
        
        '
            遍历了一下 lastProps,这个遍历主要是将旧的组件的样式,属性事件进行解绑归零。
        '
        for (propKey in lastProps) {
          if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey)) {
            continue;
          }
          
          '
            如果属性 propKey 为 style,也就是处理 样式。
            _previousStyleCopy 属性是 构造函数ReactDOMComponent的属性,
            _createOpenTagMarkupAndPutListeners方法为 _previousStyleCopy属性赋值了。
          '
          if (propKey === STYLE) {
            var lastStyle = this._previousStyleCopy;
            
            '
                遍历 存储样式的对象。
            '
            for (styleName in lastStyle) {
              if (lastStyle.hasOwnProperty(styleName)) {
              
                '
                    styleUpdates清空
                '
                styleUpdates = styleUpdates || {};
                styleUpdates[styleName] = '';
              }
            }
            this._previousStyleCopy = null;
          } else if (registrationNameModules.hasOwnProperty(propKey)) {
            if (lastProps[propKey]) {
                '
                    将绑定的回调函数删除。
                '
              deleteListener(this._rootNodeID, propKey);
            }
          } else if (DOMProperty.properties[propKey] || DOMProperty.isCustomAttribute(propKey)) {
            if (!node) {
              node = ReactMount.getNode(this._rootNodeID);
            }
            '
                作用就是删除节点上的属性值。这些属性是除开样式(style)事件之类的属性。
                比如说是 autoComplete checked等,这些属性也有很多,设定在 HTMLDOMPropertyConfig模块以及SVGDOMPropertyConfig 模块里。
            '
            DOMPropertyOperations.deleteValueForProperty(node, propKey);
          }
        }
        
        '
            前边将旧的组件的属性清理完毕之后,这里着手处理新的组件属性(props)
        '
        for (propKey in nextProps) {
          '
            拿到 nextProp 与 lastProp
          '
          var nextProp = nextProps[propKey];
          var lastProp = propKey === STYLE ? this._previousStyleCopy : lastProps[propKey];
          if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
            continue;
          }
          
          '
            如果属性是 style的话,就对比一下新旧的style,最后得出来一个 styleUpdates,是一个新的 style
          '
          if (propKey === STYLE) {
            if (nextProp) {
              if (process.env.NODE_ENV !== 'production') {
                checkAndWarnForMutatedStyle(this._previousStyleCopy, this._previousStyle, this);
                this._previousStyle = nextProp;
              }
              nextProp = this._previousStyleCopy = 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._rootNodeID, propKey, nextProp, transaction);
            } else if (lastProp) {
            
                '
                没有nextProp 就表示,这个事件监听器被取消了,这里自然要将旧组件上注册的事件监听器也给取消了。
                '
              deleteListener(this._rootNodeID, propKey);
            }
          } 
          '
            这里判断,标签中是否包含 '-',我们一般使用的html标签从没有见过'-'.
          '
          else if (isCustomComponent(this._tag, nextProps)) {
            if (!node) {
              node = ReactMount.getNode(this._rootNodeID);
            }
            if (propKey === CHILDREN) {
              nextProp = null;
            }
            DOMPropertyOperations.setValueForAttribute(node, propKey, nextProp);
          } else if (DOMProperty.properties[propKey] || DOMProperty.isCustomAttribute(propKey)) {
            '
                判断 属性(propKey)是否与DOMProperty. properties中定义的一致。
                isCustomAttribute 方法的作用是判断属性是不是 React定义的,比如 data-reactid等。
            '
            if (!node) {
              node = ReactMount.getNode(this._rootNodeID);
            }
         
            if (nextProp != null) {
              DOMPropertyOperations.setValueForProperty(node, propKey, nextProp);
            } else {
              DOMPropertyOperations.deleteValueForProperty(node, propKey);
            }
          }
        }
        
        '
            这里是 如果 styleUpdates 存在,就是说明有要更新的样式。
            CSSPropertyOperations.setValueForStyles方法负责验证样式,与将样式施加到node上。
        '
        if (styleUpdates) {
          if (!node) {
            node = ReactMount.getNode(this._rootNodeID);
          }
          
          CSSPropertyOperations.setValueForStyles(node, styleUpdates);
        }
      },
    

以上是 组件的更新方法,下边的是挂载实例的卸载组件的方法。

unmountComponent

这个方法会被递归调用,每一个组件都会有这个方法,一个组件在调用这个方法的时候会遍历其子组件,而其子组件也会一个个的去调用自身的 unmountComponent 方法,以此类推,将当前组件极其子组件都卸载掉。

    unmountComponent: function () {
    
         switch (this._tag) {
            ...
            
            '
            
            '
            case 'input':
            ReactDOMInput.unmountWrapper(this);
            break;
             
        }
        
        '
            
        '
        this.unmountChildren();
        
        '
            deleteAllListeners方法会卸载组件的事件侦听器。
            unmountIDFromEnvironment方法会调用ReactMount.purgeID(rootNodeID);方法而后删除 nodeCache 里 的缓存。
            
        '
        ReactBrowserEventEmitter.deleteAllListeners(this._rootNodeID);
        ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
        
        this._rootNodeID = null;
        this._wrapperState = null;
        if (this._nodeWithLegacyProperties) {
          var node = this._nodeWithLegacyProperties;
          node._reactInternalComponent = null;
          this._nodeWithLegacyProperties = null;
        }
        
    }

对于 input 标签要而外的进行处理。

ReactDOMInput.unmountWrapper

在mountComponent的时候将当前挂载实例放在instancesByReactID 对象中,这里将其移除。

    unmountWrapper: function (inst) {
        delete instancesByReactID[inst._rootNodeID];
    },
ReactMultiChild模块unmountChildren

这个方法的作用在于,当前组件卸载的时候,卸载组件的全部子组件。

    unmountChildren: function () {
        '
            拿到 renderedChildren,也就是要卸载组件的子组件
        '
        var renderedChildren = this._renderedChildren;
        '
            这个方法才是真正的卸载子组件的方法。
        '
        ReactChildReconciler.unmountChildren(renderedChildren);
        '
            _renderedChildren置为空。
        '
        this._renderedChildren = null;
    },
ReactChildReconciler.unmountChildren

主要的作用是遍历 renderedChildren,而后每一项调用 ReactReconciler.unmountComponent方法来进行卸载。

    unmountChildren: function (renderedChildren) {
        for (var name in renderedChildren) {
          if (renderedChildren.hasOwnProperty(name)) {
            var renderedChild = renderedChildren[name];
            ReactReconciler.unmountComponent(renderedChild);
          }
        }
    }
    
ReactReconciler.unmountComponent
    unmountComponent: function (internalInstance) {
        '
            删除ref,这里internalInstance 是要卸载组件的子组件的实例。
        '
        ReactRef.detachRefs(internalInstance, internalInstance._currentElement);
        '
            这里是调用子组件的unmountComponent方法进行卸载。
            这是一个递归调用的过程。
        '
        internalInstance.unmountComponent();
    },
ReactRef.detachRefs

这里是用来删除 ref。ref就是对组件的引用。

    ReactRef.detachRefs = function (instance, element) {
          if (element === null || element === false) {
            return;
          }
          var ref = element.ref;
          if (ref != null) {
            detachRef(ref, instance, element._owner);
          }
    };

getPublicInstance

getPublicInstance方法是挂载实例的一个重要的方法。 这个方法返回一个node,为这个node挂载了很多的方法。

    getPublicInstance: function () {
        if (!this._nodeWithLegacyProperties) {
          var node = ReactMount.getNode(this._rootNodeID);
    
          node._reactInternalComponent = this;
          node.getDOMNode = legacyGetDOMNode;
          node.isMounted = legacyIsMounted;
          node.setState = legacySetStateEtc;
          node.replaceState = legacySetStateEtc;
          node.forceUpdate = legacySetStateEtc;
          node.setProps = legacySetProps;
          node.replaceProps = legacyReplaceProps;
    
          if (process.env.NODE_ENV !== 'production') {
            if (canDefineProperty) {
              Object.defineProperties(node, legacyPropsDescriptor);
            } else {
              // updateComponent will update this property on subsequent renders
              node.props = this._currentElement.props;
            }
          } else {
            // updateComponent will update this property on subsequent renders
            node.props = this._currentElement.props;
          }
    
          this._nodeWithLegacyProperties = node;
        }
        return this._nodeWithLegacyProperties;
      }

总结

当element不为 null或者是false,而且,element.type为string类型的时候,也就是组件为html标签的时候,React会为这些组件采用 ReactDOMComponent类生成挂载实例(这里。

123便是一个组件,生成一个挂载实例。)。

挂载实例有一些比较重要的方法,这些方法是在 element.type为string的时候才拥有的,不同的 element会生成不同的挂载实例,挂载实例的方法也是不同的。