从react源码看Virtual Dom到真实Dom的渲染过程
很多人都看过许多React的Virtual Dom的文章,背熟了好多生命周期函数,然而,对于一个Virtual Dom渲染成一个真实Dom的过程你是否真的研究过呢?
ReactDOM.render(
<h1>Hello World</h1>,
document.getElementById('root')
);
以上代码用babel转义过来就是:
ReactDOM.render(React.createElement(
'h1',
null,
'Hello World'
), document.getElementById('root'));
ReactElement就是我们常说的Virtual Dom,下面我们将讨论react将一个ReactElement渲染成真实Dom的过程。
我们先看一下ReactElement.js源码:链接
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;
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;
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
};
以下是我们创建的Hello World的ReactElement
{
$$typeof: Symbol(react.element)
key: null
props: {children: "Hello World"}
ref: null
type: "h1"
_owner: null
_store: {validated: false}
_self: null
_source: null
__proto__: Object
}
接下来我们看render函数源码:链接
在render里面,调用_renderSubtreeIntoContainer,_renderSubtreeIntoContainer里又调用_renderNewRootComponent,_renderNewRootComponent生成一个ReactCompositeComponentWrapper并返回。
一个很重要的数据结构:ReactCompositeComponentWrapper
instantiateReactComponent方法本质上是个工厂函数,它在内部会对ReactElement类型进行判断,返回一个ReactCompositeComponentWrapper。
我么来看instantiateReactComponent源码:
function instantiateReactComponent(node, shouldHaveDebugID) {
var instance;
if (node === null || node === false) {
// 1 ReactDOMEmptyComponent:空对象
instance = ReactEmptyComponent.create(instantiateReactComponent);
} else if (typeof node === 'object') {
if (typeof element.type === 'string') {
// 2 ReactDOMComponent:DOM原生对象
instance = ReactHostComponent.createInternalComponent(element);
} else if (isInternalComponentType(element.type)) {
instance = new element.type(element);
if (!instance.getHostNode) {
instance.getHostNode = instance.getNativeNode;
}
} else {
// 3 ReactCompositeComponent:React自定义对象
instance = new ReactCompositeComponentWrapper(element);
}
} else if (typeof node === 'string' || typeof node === 'number') {
// 4 ReactDOMTextComponent:文本对象
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;
}
return instance;
}
一个ReactCompositeComponentWrapper长这样:
{
_calledComponentWillUnmount: false
_compositeType: null
_context: null
// 包了一层的ReactElement
_currentElement: {$$typeof: Symbol(react.element), key: null, ref: null, props: {…}, type: ƒ, …}
_debugID: 0
_hostContainerInfo: null
_hostParent: null
_instance: null
_mountImage: null
_mountIndex: 0
_mountOrder: 0
_pendingCallbacks: null
_pendingElement: null
_pendingForceUpdate: false
_pendingReplaceState: false
_pendingStateQueue: null
_renderedComponent: null
_renderedNodeType: null
_rootNodeID: 0
_topLevelWrapper: null
_updateBatchNumber: null
_warnedAboutRefsInRender: false
__proto
}
接下来是我认为最复杂的一部分,也就是负责将ReactCompositeComponentWrapper渲染成真实Dom的部分
_renderNewRootComponent源码:
_renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case.
process.env.NODE_ENV !== 'production' ? warning(ReactCurrentOwner.current == null, '_renderNewRootComponent(): Render methods should be a pure function ' + 'of props and state; triggering nested component updates from ' + 'render is not allowed. If necessary, trigger nested updates in ' + 'componentDidUpdate. Check the render method of %s.', ReactCurrentOwner.current && ReactCurrentOwner.current.getName() || 'ReactCompositeComponent') : void 0;
!isValidContainer(container) ? process.env.NODE_ENV !== 'production' ? invariant(false, '_registerComponent(...): Target container is not a DOM element.') : _prodInvariant('37') : void 0;
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
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);
var wrapperID = componentInstance._instance.rootID;
instancesByReactRootID[wrapperID] = componentInstance;
return componentInstance;
}
componentInstance是由instantiateReactComponent生成的一个ReactCompositeComponentWrapper。
在ReactUpdates.batchedUpdates里面,通过mountComponent方法将ReactCompositeComponentWrapper转化为一个markup
一个markup长这样:
{
children:[]
html:null
node:h1
text:null
toString:ƒ toString()
__proto__:Object
}
markup通过mountComponentIntoNode、_mountImageIntoNode最终渲染为真实Dom!!!
知道了这么个过程,也许对后面研究生命周期,diff函数有很大的帮助吧。
相关链接:React源码系列之初次渲染