React组件是如何渲染的
问题如题,虽然之前也有过这个疑惑,但一直就放在那。直到最近看Redux相关的文章,看到这么一段代码:
// CONTAINER
export const view = connect(
state => ({count: state})
)(
({name, count, dispatch}) => <div>
<p>{name}:
{count}
<button onClick={e => dispatch(decrement())}>-</button>
<button onClick={e => dispatch(increment())}>+</button>
</p>
</div>
)
其中connect
函数最后面的输入是一个React组件对象,在这里它其实就是一个很简单的函数返回了一个DOM对象。React组件到底都支持哪些形式呢?
React源码剖析1
要看React组件支持哪些形式,其实主要是看在进行组件渲染时(将virtual DOM渲染成真实的DOM),React是怎么做的。以下内容会比较啰嗦,TL;DR,可以直接看结论。
我们在渲染React组件时首先会调用ReactDOM.render
,像下面这样将我们的组件作为参数输入进去:
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App/>,
document.getElementById('root')
);
顺着ReactDOM.render
,我们来看React是怎么实现的。在ReactMount.js
中,下面的nextElement
就是我们输入的React组件(准确地说是被编译成ReactElement
类型的对象):
_renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) {
// 省略若干代码
var nextWrappedElement = React.createElement(TopLevelWrapper, { child: nextElement });
// 省略若干代码
var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)._renderedComponent.getPublicInstance();
if (callback) {
callback.call(component);
}
return component;
}
首先这个TopLevelWrapper
是下面这样子的(注意它原型的render
函数返回的就是我们的React组件对象),这个函数对象对应了后面的ReactElement
对象的type
属性。
var topLevelRootCounter = 1;
var TopLevelWrapper = function () {
this.rootID = topLevelRootCounter++;
};
TopLevelWrapper.prototype.isReactComponent = {};
if (process.env.NODE_ENV !== 'production') {
TopLevelWrapper.displayName = 'TopLevelWrapper';
}
TopLevelWrapper.prototype.render = function () {
return this.props.child;
};
TopLevelWrapper.isReactTopLevelWrapper = true;
然后来看React.createElement
,它最后会返回一个ReactElement
对象,其中我们的React组件就是输入参数的props.child
:
var ReactElement = function (type, key, ref, self, source, owner, props) {
var element = {
// This tag allow us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
};
// 省略若干代码
return element;
}
可以看到到此为止,React组件的render
函数并没有被调用,它所做的只是把组件的一些相关的信息提取了出来。
现在来看提取了这些信息的ReactElement
对象的后续处理,即ReactMount._renderNewRootComponent
函数(这里输入的nextElement
就是包含了我们React组件的对象):
_renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
// 省略若干代码
var componentInstance = instantiateReactComponent(nextElement, false);
// The initial render is synchronous but any updates that happen during
// rendering, in componentWillMount or componentDidMount, will be batched
// according to the current batching strategy.
ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);
// 省略若干代码
return componentInstance;
}
继续追instantiateReactComponent
函数,看它的函数注释,我感觉我们找对地方了。这里我把整个函数都摘出来了:
/**
* Given a ReactNode, create an instance that will actually be mounted.
*
* @param {ReactNode} node
* @param {boolean} shouldHaveDebugID
* @return {object} A new instance of the element's constructor.
* @protected
*/
function instantiateReactComponent(node, shouldHaveDebugID) {
var instance;
if (node === null || node === false) {
instance = ReactEmptyComponent.create(instantiateReactComponent);
} else if (typeof node === 'object') {
var element = node;
var type = element.type;
if (typeof type !== 'function' && typeof type !== 'string') {
var info = '';
if (process.env.NODE_ENV !== 'production') {
if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) {
info += ' You likely forgot to export your component from the file ' + 'it\'s defined in.';
}
}
info += getDeclarationErrorAddendum(element._owner);
!false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s', type == null ? type : typeof type, info) : _prodInvariant('130', type == null ? type : typeof type, info) : void 0;
}
// Special case string values
if (typeof element.type === 'string') {
instance = ReactHostComponent.createInternalComponent(element);
} else if (isInternalComponentType(element.type)) {
// This is temporarily available for custom components that are not string
// representations. I.e. ART. Once those are updated to use the string
// representation, we can drop this code path.
instance = new element.type(element);
// We renamed this. Allow the old name for compat. :(
if (!instance.getHostNode) {
instance.getHostNode = instance.getNativeNode;
}
} else {
instance = new ReactCompositeComponentWrapper(element);
}
} else if (typeof node === 'string' || typeof node === 'number') {
instance = ReactHostComponent.createInstanceForText(node);
} else {
!false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Encountered invalid React node of type %s', typeof node) : _prodInvariant('131', typeof node) : void 0;
}
if (process.env.NODE_ENV !== 'production') {
process.env.NODE_ENV !== 'production' ? warning(typeof instance.mountComponent === 'function' && typeof instance.receiveComponent === 'function' && typeof instance.getHostNode === 'function' && typeof instance.unmountComponent === 'function', 'Only React Components can be mounted.') : void 0;
}
// These two fields are used by the DOM and ART diffing algorithms
// respectively. Instead of using expandos on components, we should be
// storing the state needed by the diffing algorithms elsewhere.
instance._mountIndex = 0;
instance._mountImage = null;
if (process.env.NODE_ENV !== 'production') {
instance._debugID = shouldHaveDebugID ? getNextDebugID() : 0;
}
// Internal instances should fully constructed at this point, so they should
// not get any new fields added to them at this point.
if (process.env.NODE_ENV !== 'production') {
if (Object.preventExtensions) {
Object.preventExtensions(instance);
}
}
return instance;
}
以上,可以看到根据ReactElement
对象类型的不同,会生成不同的对象,而这里我们从上面一路走下来得到的ReactElement
对象大概是长这样的:
var node = {
type: TopLevelWrapper, // A function
props: {
child: ourReactComponent, // input React component
}
// 省略若干属性
}
所以,我们只需要关注这么一句就可以了:
instance = new ReactCompositeComponentWrapper(element);
那么就看ReactCompositeComponentWrapper
的定义:
var ReactCompositeComponentWrapper = function (element) {
this.construct(element);
};
_assign(ReactCompositeComponentWrapper.prototype, ReactCompositeComponent, {
_instantiateReactComponent: instantiateReactComponent
});
这里的_assign
方法可以看作是Object.assign
的polyfill的函数,所以this.construct
函数实际调用的是ReactCompositeComponent.construct
函数。
var ReactCompositeComponent = {
/**
* Base constructor for all composite component.
*
* @param {ReactElement} element
* @final
* @internal
*/
construct: function (element) {
this._currentElement = element;
this._rootNodeID = 0;
this._compositeType = null;
this._instance = null;
this._hostParent = null;
this._hostContainerInfo = null;
// See ReactUpdateQueue
this._updateBatchNumber = null;
this._pendingElement = null;
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
this._renderedNodeType = null;
this._renderedComponent = null;
this._context = null;
this._mountOrder = 0;
this._topLevelWrapper = null;
// See ReactUpdates and ReactUpdateQueue.
this._pendingCallbacks = null;
// ComponentWillUnmount shall only be called once
this._calledComponentWillUnmount = false;
if (process.env.NODE_ENV !== 'production') {
this._warnedAboutRefsInRender = false;
}
},
// 省略其他属性
}
可以看到,其实这里几乎什么也没做,就是把我们的ReactElement
对象作为_currentElement
属性放在了新生成的这个对象中。所以,直到这里,我们定义的React组件还没有被渲染出来。
那么回到之前的ReactMount._renderNewRootComponent
函数中,得到了上面的对象后,还剩执行了这么一句话(其中componentInstance
即为上面生成的对象):
ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);
其中batchedUpdates
函数:
function batchedUpdates(callback, a, b, c, d, e) {
ensureInjected();
return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}
但当我们想看batchingStrategy
的定义时,发现:
var batchingStrategy = null;
也就是说这个batchingStrategy
是在别的地方初始化的,大致找了一下,通过ReactUpdates.injection.injectBatchingStrategy
函数可以初始化它,这个函数会在ReactServerRendering.js
中被调用,并且会把ReactDefaultBatchingStrategy
作为默认的对象初始化它。
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
/**
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};
transaction.perform
:
perform: function (method, scope, a, b, c, d, e, f) {
!!this.isInTransaction() ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Transaction.perform(...): Cannot initialize a transaction when there is already an outstanding transaction.') : _prodInvariant('27') : void 0;
var errorThrown;
var ret;
try {
this._isInTransaction = true;
// Catching errors makes debugging more difficult, so we start with
// errorThrown set to true before setting it to false after calling
// close -- if it's still set to true in the finally block, it means
// one of these calls threw.
errorThrown = true;
this.initializeAll(0);
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
if (errorThrown) {
// If `method` throws, prefer to show that stack trace over any thrown
// by invoking `closeAll`.
try {
this.closeAll(0);
} catch (err) {}
} else {
// Since `method` didn't throw, we don't want to silence the exception
// here.
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
},
搞了半天,其实就是调用了之前的batchedMountComponentIntoNode
:
function batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context) {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
/* useCreateElement */
!shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement);
transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);
ReactUpdates.ReactReconcileTransaction.release(transaction);
}
这里又绕了一下,调用的是mountComponentIntoNode
:
function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReuseMarkup, context) {
var markerName;
if (ReactFeatureFlags.logTopLevelRenders) {
var wrappedElement = wrapperInstance._currentElement.props.child;
var type = wrappedElement.type;
markerName = 'React mount: ' + (typeof type === 'string' ? type : type.displayName || type.name);
console.time(markerName);
}
var markup = ReactReconciler.mountComponent(wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0 /* parentDebugID */
);
if (markerName) {
console.timeEnd(markerName);
}
wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
ReactMount._mountImageIntoNode(markup, container, wrapperInstance, shouldReuseMarkup, transaction);
}
注意这里的wrapperInstance
就是我们之前得到的包含了我们的React组件的那个对象,wrapperInstance._currentElement.props.child
就是我们的React组件。
再看ReactReconciler.mountComponent
函数,从函数定义来看,它的返回值是一个markup的string,也就是渲染后我们从网页上看到的我们的React组件的样子。
mountComponent: function (internalInstance, transaction, hostParent, hostContainerInfo, context, parentDebugID // 0 in production and for roots
) {
// 省略debug用代码
var markup = internalInstance.mountComponent(transaction, hostParent, hostContainerInfo, context, parentDebugID);
if (internalInstance._currentElement && internalInstance._currentElement.ref != null) {
transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
}
// 省略debug用代码
return markup;
},
注意到这里调用了之前得到的包裹了我们React组件的对象的mountComponent
函数,这个函数长这样(在ReactCompositeComponent.js
中):
mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
var _this = this;
this._context = context;
this._mountOrder = nextMountID++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;
var publicProps = this._currentElement.props;
var publicContext = this._processContext(context);
var Component = this._currentElement.type;
var updateQueue = transaction.getUpdateQueue();
// Initialize the public class
var doConstruct = shouldConstruct(Component);
var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
var renderedElement;
// Support functional components
if (!doConstruct && (inst == null || inst.render == null)) {
renderedElement = inst;
warnIfInvalidElement(Component, renderedElement);
!(inst === null || inst === false || React.isValidElement(inst)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.', Component.displayName || Component.name || 'Component') : _prodInvariant('105', Component.displayName || Component.name || 'Component') : void 0;
inst = new StatelessComponent(Component);
this._compositeType = CompositeTypes.StatelessFunctional;
} else {
if (isPureComponent(Component)) {
this._compositeType = CompositeTypes.PureClass;
} else {
this._compositeType = CompositeTypes.ImpureClass;
}
}
// 省略debug用代码
// These should be set up in the constructor, but as a convenience for
// simpler class abstractions, we set them up after the fact.
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = updateQueue;
this._instance = inst;
// Store a reference from the instance back to the internal representation
ReactInstanceMap.set(inst, this);
// 省略debug用代码
var initialState = inst.state;
if (initialState === undefined) {
inst.state = initialState = null;
}
!(typeof initialState === 'object' && !Array.isArray(initialState)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s.state: must be set to an object or null', this.getName() || 'ReactCompositeComponent') : _prodInvariant('106', this.getName() || 'ReactCompositeComponent') : void 0;
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;
var markup;
if (inst.unstable_handleError) {
markup = this.performInitialMountWithErrorHandling(renderedElement, hostParent, hostContainerInfo, transaction, context);
} else {
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
}
if (inst.componentDidMount) {
if (process.env.NODE_ENV !== 'production') {
transaction.getReactMountReady().enqueue(function () {
measureLifeCyclePerf(function () {
return inst.componentDidMount();
}, _this._debugID, 'componentDidMount');
});
} else {
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
}
return markup;
},
这里有几点值得注意:
-
React Support functional components. 这点注释上就有,当判断出我们的React组件并不符合React标准的组件时,就会被当做是函数组件,此时
renderedElement = inst;
,并且它的组件类型被标记为了StatelessFunctional
-
当我们的React组件符合标准时,
renderedElement
是undefined
;此时组件类型标被记为PureClass
或ImpureClass
-
最关键的一句在这里:
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
-
渲染成markup后,会调用
componentDidMount
(如果有这个方法的话):transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
那么来看最关键的那句中的函数:
performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) {
var inst = this._instance;
var debugID = 0;
if (process.env.NODE_ENV !== 'production') {
debugID = this._debugID;
}
if (inst.componentWillMount) {
if (process.env.NODE_ENV !== 'production') {
measureLifeCyclePerf(function () {
return inst.componentWillMount();
}, debugID, 'componentWillMount');
} else {
inst.componentWillMount();
}
// When mounting, calls to `setState` by `componentWillMount` will set
// `this._pendingStateQueue` without triggering a re-render.
if (this._pendingStateQueue) {
inst.state = this._processPendingState(inst.props, inst.context);
}
}
// If not a stateless component, we now render
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
var nodeType = ReactNodeTypes.getType(renderedElement);
this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */);
this._renderedComponent = child;
var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);
// 省略debug用代码
return markup;
},
-
组件的
componentWillMount
函数在这里被调用了(如果定义了的话); -
从上面可知,传递进来的
renderedElement=undefined
,因此在这里会重新生成这个对象:renderedElement = this._renderValidatedComponent();
再往里面追会发现它其实就是调用了
inst.render()
函数得到的; -
还记得这个
inst
是什么吗?在这里是TopLevelWrapper
new出来的对象,因此inst.render()
得到的就是我们的React组件对象(所以是包了一层嘛); -
最后这里最关键的一句是:
var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);
还记得
ReactReconciler.mountComponent
函数之前出现过么?我们之所以追踪到这里就是通过这个函数来的!这里把当前组件的子孙组件传递进去了,形成了递归,从而可以达到渲染整个DOM tree的结果(包含真实DOM和虚拟DOM)。注意这里是单数形式的child
,也就是说React virtual DOM只会有一个子孙节点,即我们的React组件的render函数必须返回的是单个节点(当然这个节点可以保护多个子节点,但最外部只能有一个根节点)。
以上,对于React组件的渲染过程基本走了一遍,但在渲染React组件时通常是会夹杂一些真实的DOM的,对于这类元素,React是怎么做的呢?
对于真实的DOM,React中实例化后是类型为ReactDOMComponent
的对象,它当然也有一个mountComponent
的函数作为属性(其实就类似多态啦),还记得之前的ReactReconciler.mountComponent
函数吗,它会去调用组件对象的这个函数:
ReactDOMComponent.Mixin = {
mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
this._rootNodeID = globalIdCounter++;
this._domID = hostContainerInfo._idCounter++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;
var props = this._currentElement.props;
// 省略一些特殊case的处理代码
assertValidProps(this, props);
// We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
var namespaceURI;
var parentTag;
if (hostParent != null) {
namespaceURI = hostParent._namespaceURI;
parentTag = hostParent._tag;
} else if (hostContainerInfo._tag) {
namespaceURI = hostContainerInfo._namespaceURI;
parentTag = hostContainerInfo._tag;
}
if (namespaceURI == null || namespaceURI === DOMNamespaces.svg && parentTag === 'foreignobject') {
namespaceURI = DOMNamespaces.html;
}
if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'svg') {
namespaceURI = DOMNamespaces.svg;
} else if (this._tag === 'math') {
namespaceURI = DOMNamespaces.mathml;
}
}
this._namespaceURI = namespaceURI;
// 省略一些debug用代码
var mountImage;
if (transaction.useCreateElement) {
var ownerDocument = hostContainerInfo._ownerDocument;
var el;
if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'script') {
// Create the script via .innerHTML so its "parser-inserted" flag is
// set to true and it does not execute
var div = ownerDocument.createElement('div');
var type = this._currentElement.type;
div.innerHTML = '<' + type + '></' + type + '>';
el = div.removeChild(div.firstChild);
} else if (props.is) {
el = ownerDocument.createElement(this._currentElement.type, props.is);
} else {
// Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
// See discussion in https://github.com/facebook/react/pull/6896
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
el = ownerDocument.createElement(this._currentElement.type);
}
} else {
el = ownerDocument.createElementNS(namespaceURI, this._currentElement.type);
}
ReactDOMComponentTree.precacheNode(this, el);
this._flags |= Flags.hasCachedChildNodes;
if (!this._hostParent) {
DOMPropertyOperations.setAttributeForRoot(el);
}
this._updateDOMProperties(null, props, transaction);
var lazyTree = DOMLazyTree(el);
this._createInitialChildren(transaction, props, context, lazyTree);
mountImage = lazyTree;
} else {
var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props);
var tagContent = this._createContentMarkup(transaction, props, context);
if (!tagContent && omittedCloseTags[this._tag]) {
mountImage = tagOpen + '/>';
} else {
mountImage = tagOpen + '>' + tagContent + '</' + this._currentElement.type + '>';
}
}
// 省略一些特殊case的处理代码
return mountImage;
},
}
这里最关键的是这一句:
this._createInitialChildren(transaction, props, context, lazyTree);
执行完这一句会陆续把整个DOM tree渲染出来。再看它的实现:
_createInitialChildren: function (transaction, props, context, lazyTree) {
// Intentional use of != to avoid catching zero/false.
var innerHTML = props.dangerouslySetInnerHTML;
if (innerHTML != null) {
if (innerHTML.__html != null) {
DOMLazyTree.queueHTML(lazyTree, innerHTML.__html);
}
} else {
var contentToUse = CONTENT_TYPES[typeof props.children] ? props.children : null;
var childrenToUse = contentToUse != null ? null : props.children;
// TODO: Validate that text is allowed as a child of this node
if (contentToUse != null) {
// Avoid setting textContent when the text is empty. In IE11 setting
// textContent on a text area will cause the placeholder to not
// show within the textarea until it has been focused and blurred again.
// https://github.com/facebook/react/issues/6731#issuecomment-254874553
if (contentToUse !== '') {
if (process.env.NODE_ENV !== 'production') {
setAndValidateContentChildDev.call(this, contentToUse);
}
DOMLazyTree.queueText(lazyTree, contentToUse);
}
} else if (childrenToUse != null) {
var mountImages = this.mountChildren(childrenToUse, transaction, context);
for (var i = 0; i < mountImages.length; i++) {
DOMLazyTree.queueChild(lazyTree, mountImages[i]);
}
}
}
},
这里很明显是要看这句:
var mountImages = this.mountChildren(childrenToUse, transaction, context);
它的实现在ReactMultiChild.js
里:
mountChildren: function (nestedChildren, transaction, context) {
var children = this._reconcilerInstantiateChildren(nestedChildren, transaction, context);
this._renderedChildren = children;
var mountImages = [];
var index = 0;
for (var name in children) {
if (children.hasOwnProperty(name)) {
var child = children[name];
var selfDebugID = 0;
if (process.env.NODE_ENV !== 'production') {
selfDebugID = getDebugID(this);
}
var mountImage = ReactReconciler.mountComponent(child, transaction, this, this._hostContainerInfo, context, selfDebugID);
child._mountIndex = index++;
mountImages.push(mountImage);
}
}
if (process.env.NODE_ENV !== 'production') {
setChildrenForInstrumentation.call(this, children);
}
return mountImages;
},
看到了没?ReactReconciler.mountComponent
在这里又被调用了!这次会遍历当前DOM元素对象的所有下一级子孙元素对象,并且将他们逐一渲染出来,再次形成递归。
结论###
-
正如官方文档所言,所有的组件在编译后都会得到
ReactElement
类型的对象,它是组件在内存中真实的样子; -
对
ReactElement
对象,React使用两个不同的类来分别处理虚拟DOM和真实DOM的载入等操作,对于虚拟DOM,这个类是ReactCompositeComponent
,对真实的DOM,这个类叫ReactDOMComponent
; -
如果组件中包含类似
componentWillMount
这些hook的函数作为属性,则React会在相应的时间去执行它们,如果没有这些属性,则什么都不会发生。另外插一句,(目前)JavaScript里面好像还没有抽象类、抽象函数的概念?所以hook函数没法直接看继承的类的定义来获取(比如React.Component
里面是没有componentWillMount
的影子的),所以只能靠看官方文档了; -
对于虚拟DOM而言,React把它们分成了三类:
StatelessFunctional
、PureClass
和ImpureClass
; -
React支持直接用一个函数来表示组件(即上面的
StatelessFunctional
),此时函数本身就相当于普通组件的render
函数,而其他所有属性都被缺省了; -
React组件的
render
函数返回的虚拟DOM只能包含一个根节点,即只能是一颗树状的结构,不能是森林状的结构,即使要表现多个节点,也要用一个根节点把它们包起来,这是React官方限制的; -
真实的DOM是可以包含多个子节点的,它的每个子节点会被遍历逐一渲染;
-
React中组件(或元素) 的渲染是自顶向下的,即最外部的元素会被先渲染出来;
-
React组件渲染的流程粗略为以下样子:
-
react
和react-dom
均以“15.4.2”版本为准。 ↩
Comments