ReactDOM.render(
<h1>Hello, world!h1>,
document.getElementById('example')
);
ReactDOM.render()
实际调用ReactMount.render()
/**
* @param {ReactElement} nextElement 要插入到DOM中的组件
* @param {DOMElement} container 要插入到的容器
* @param {?function} callback 回调
* @return {ReactComponent} Component instance rendered in `container`.返回ReactComponent
*/
render: function (nextElement, container, callback) {
},
可见render方法的第一个参数是一个ReactElement
类型的变量,那么
是如何转成成为Hello, world!
ReactElement
类型变量的呢?快往下看呀
JSX中创建React元素最终会被babel转译为createElement(type, config, children)
, babel根据JSX中标签的首字母来判断是原生DOM组件,还是自定义React组件
"wrap" >
<span>123span>
<My className='my' content={456} />
div>
上面这段代码转义完后如下:
React.createElement(
type: 'div',
props:{
className: 'wrap',
},
children: [
React.createElement(
type: 'span',
props: null,
chilren: '123'
),
React.createElement(
type:My.default//从其他文件中引入的React组件
props: {
className: 'my',
content: 456
}
)
]
)
所以createElement
做了什么呢?
/**
* Factory method to create a new React element. This no longer adheres to
* the class pattern, so do not use new to call it. Also, no instanceof check
* will work. Instead test $$typeof field against Symbol.for('react.element') to check
* if something is a React Element.
*
* @param {*} type
* @param {*} key
* @param {string|object} ref
* @param {*} self A *temporary* helper to detect places where `this` is
* different from the `owner` when React.createElement is called, so that we
* can warn. We want to get rid of owner and replace string `ref`s with arrow
* functions, and as long as `this` and owner are the same, there will be no
* change in behavior.
* @param {*} source An annotation object (added by a transpiler or otherwise)
* indicating filename, line number, and/or other information.
* @param {*} owner
* @param {*} props
* @internal
*/
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元素节点
*/
ReactElement.createElement = function(type, config, children) {
var propName;
// Reserved names are extracted
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
// 传过来的children被放在了props
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
// 静态变量defaultProps,属性设置默认值
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
};
由此可见,createElement()
接收三个参数(type,config,children),做了一些变量初始化,接着调用了ReactElement()
方法。
ReactElement()
是一个工厂方法,根据传入的参数返回一个element对象,也是我们一直所说的ReactElement
,如下
接下来我们来看一看render()
做了什么
这是一串函数
告诉自己,我不晕
/**
* Mounting is the process of initializing a React component by creating its
* representative DOM elements and inserting them into a supplied `container`.
* Any prior content inside `container` is destroyed in the process.
*
* ReactMount.render(
* component,
* document.getElementById('container')
* );
*
* <-- Supplied `container`.
* <-- Rendered reactRoot of React
* // ... component.
*
*
*
* Inside of `container`, the first element rendered is the "reactRoot".
*/
var ReactMount = {
/**入口render方法
* @param {ReactElement} nextElement 要插入到DOM中的组件
* @param {DOMElement} container 要插入到的容器
* @param {?function} callback 回调
* @return {ReactComponent} Component instance rendered in `container`.返回ReactComponent
*/
render: function (nextElement, container, callback) {
return ReactMount._renderSubtreeIntoContainer(
null,
nextElement,
container,
callback,
);
},
/**
* 将ReactElement插入DOM中,并返回ReactElement对应的ReactComponent。
* ReactElement是React元素在内存中的表示形式,可以理解为一个数据类,包含type,key,refs,props等成员变量
* ReactComponent是React元素的操作类,包含mountComponent(), updateComponent()等很多操作组件的方法
*/
_renderSubtreeIntoContainer: function (
parentComponent,
nextElement,
container,
callback,
) {
callback = callback === undefined ? null : callback;
var nextWrappedElement = React.createElement(TopLevelWrapper, {
child: nextElement,
});
var nextContext = getContextForSubtree(parentComponent);
// 获取要插入到的容器的前一次的ReactComponent,这是为了做DOM diff
var prevComponent = getTopLevelWrapperInContainer(container);
if (prevComponent) {
var prevWrappedElement = prevComponent._currentElement;
var prevElement = prevWrappedElement.props.child;
// shouldUpdateReactComponent方法判断是否需要更新,它只对同一DOM层级,type相同,key(如果有)相同的组件做DOM diff,
if (shouldUpdateReactComponent(prevElement, nextElement)) {
var publicInst = prevComponent._renderedComponent.getPublicInstance();
var updatedCallback =
callback &&
function () {
validateCallback(callback);
callback.call(publicInst);
};
ReactMount._updateRootComponent(
prevComponent,
nextWrappedElement,
nextContext,
container,
updatedCallback,
);
return publicInst;
} else {
//直接unmount
ReactMount.unmountComponentAtNode(container);
}
}
// 对于ReactDOM.render()调用,prevComponent为null
var reactRootElement = getReactRootElementInContainer(container);
var containerHasReactMarkup =
reactRootElement && !!internalGetID(reactRootElement);
var containerHasNonRootReactChild = hasNonRootReactChild(container);
var shouldReuseMarkup =
containerHasReactMarkup &&
!prevComponent &&
!containerHasNonRootReactChild;
var component = ReactMount._renderNewRootComponent(
nextWrappedElement,
container,
shouldReuseMarkup,
nextContext,
callback,
)._renderedComponent.getPublicInstance();
return component;
},
/**
* Render a new component into the DOM. Hooked by hooks!
*
* @param {ReactElement} nextElement element to render
* @param {DOMElement} container container to render into
* @param {boolean} shouldReuseMarkup if we should skip the markup insertion
* @return {ReactComponent} nextComponent
*/
_renderNewRootComponent: function (
nextElement,
container,
shouldReuseMarkup,
context,
callback,
) {
//初始化ReactComponent,根据ReactElement中不同的type字段,创建不同类型的组件对象,即ReactComponent
var componentInstance = instantiateReactComponent(nextElement, false);
if (callback) {
//。。。。
}
// 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.
// 处理batchedMountComponentIntoNode方法调用,将ReactComponent插入DOM中
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
container,
shouldReuseMarkup,
context,
);
var wrapperID = componentInstance._instance.rootID;
instancesByReactRootID[wrapperID] = componentInstance;
return componentInstance;
},
_mountImageIntoNode: function(
markup,
container,
instance,
shouldReuseMarkup,
transaction,
) {
//如变量名,是否复用markup,ReactDOM.render()调用为false
if (shouldReuseMarkup) {
var rootElement = getReactRootElementInContainer(container);
if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
ReactDOMComponentTree.precacheNode(instance, rootElement);
return;
} else {
var checksum = rootElement.getAttribute(
ReactMarkupChecksum.CHECKSUM_ATTR_NAME,
);
rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);
var rootMarkup = rootElement.outerHTML;
rootElement.setAttribute(
ReactMarkupChecksum.CHECKSUM_ATTR_NAME,
checksum,
);
var normalizedMarkup = markup;
}
}
if (transaction.useCreateElement) {
while (container.lastChild) {
container.removeChild(container.lastChild);
}
DOMLazyTree.insertTreeBefore(container, markup, null);
} else {
// 利用innerHTML将markup插入到container这个DOM元素上
setInnerHTML(container, markup);
// 将instance(Virtual DOM)保存到container这个DOM元素的firstChild这个原生节点上
ReactDOMComponentTree.precacheNode(instance, container.firstChild);
}
},
}
/**
* Batched mount.
以transaction事务的形式调用mountComponentIntoNode
* @param {ReactComponent} componentInstance The instance to mount.
* @param {DOMElement} container DOM element to mount into.
* @param {boolean} shouldReuseMarkup If true, do not insert markup
*/
function batchedMountComponentIntoNode(
componentInstance,
container,
shouldReuseMarkup,
context,
) {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
/* useCreateElement */
!shouldReuseMarkup,
);
transaction.perform(
mountComponentIntoNode,
null,
componentInstance,
container,
transaction,
shouldReuseMarkup,
context,
);
ReactUpdates.ReactReconcileTransaction.release(transaction);
}
/**
* Mounts this component and inserts it into the DOM.
*
* @param {ReactComponent} componentInstance The instance to mount.
* @param {DOMElement} container DOM element to mount into.
* @param {ReactReconcileTransaction} transaction
* @param {boolean} shouldReuseMarkup If true, do not insert markup
*/
function mountComponentIntoNode(
wrapperInstance,
container,
transaction,
shouldReuseMarkup,
context,
) {
//调用对应ReactComponent中的mountComponent方法来渲染组件,返回React组件解析后的HTML
var markup = ReactReconciler.mountComponent(
wrapperInstance,
transaction,
null,
ReactDOMContainerInfo(wrapperInstance, container),
context,
0 /* parentDebugID */,
);
wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
// 将解析出来的HTML插入DOM中
ReactMount._mountImageIntoNode(
markup,
container,
wrapperInstance,
shouldReuseMarkup,
transaction,
);
}
/**
* Given a ReactNode, create an instance that will actually be mounted.
* 根据ReactElement中不同的type字段,创建不同类型的组件对象
*
* ReactEmptyComponent.create(), 创建空对象ReactDOMEmptyComponent
* ReactNativeComponent.createInternalComponent(), 创建DOM原生对象 ReactDOMComponent
* new ReactCompositeComponentWrapper(), 创建React自定义对象ReactCompositeComponent
* ReactNativeComponent.createInstanceForText(), 创建文本对象 ReactDOMTextComponent
*
* @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') {
// 组件对象,包括DOM原生的和React自定义组件
var element = node;
var type = element.type;
if (typeof element.type === 'string') {
// DOM原生对象
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 {
// React自定义组件
instance = new ReactCompositeComponentWrapper(element);
}
} else if (typeof node === 'string' || typeof node === 'number') {
// 文本对象
instance = ReactHostComponent.createInstanceForText(node);
} else {
// 报error
}
// 还记得dom diff中的_mountIndex吗?
instance._mountIndex = 0;
instance._mountImage = null;
return instance;
}
好吧,我也晕了。。
大体过程如下: