当,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类生成挂载实例(这里。
挂载实例有一些比较重要的方法,这些方法是在 element.type为string的时候才拥有的,不同的 element会生成不同的挂载实例,挂载实例的方法也是不同的。