很多人都看過許多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源碼系列之初次渲染