本文使用的是react 15.6.1的代码
ReactDOM.render
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
let {text} = this.props;
return
{text}
}
}
App.defaultProps = {
text: 'hello react'
};
ReactDOM.render((
), document.getElementById('root'));
相信大家在初学react的时候都写过类似上面的代码,上篇文章中,已经介绍了 extends React.Component
做了些什么,这次,我们就来看看ReactDOM.render中react去做了些什么,ReactDOM.render实际调用的是ReactMount.js中的render
代码地址:[ReactMount]
// nextElement即ReactElement,
// container 具体容器,将由virtualDOM生成的真实dom映射的位置
// callback 渲染成功后的回调
render: function(nextElement, container, callback) {
return ReactMount._renderSubtreeIntoContainer(
null,
nextElement,
container,
callback,
);
进一步得知,其主要逻辑位于_renderSubtreeIntoContainer下
代码地址:[ReactMount]
// ReactDom.render调用后 第一个参数默认传递null,因为该方法第一个参数是父组件,剩下三个参数和render函数一致
_renderSubtreeIntoContainer: function(
parentComponent,
nextElement,
container,
callback,
) {
//判断callback是否为函数;
ReactUpdateQueue.validateCallback(callback, 'ReactDOM.render');
var nextWrappedElement = React.createElement(TopLevelWrapper, {
child: nextElement,
});
//通过TopLevelWrapper创建一个ReactElement节点,并且设置其this.props.child = render传入的ReactElement
/** 上文TopLevelWrapper代码
var topLevelRootCounter = 1;z
var TopLevelWrapper = function() {
this.rootID = topLevelRootCounter++;
};
TopLevelWrapper.prototype.isReactComponent = {};
TopLevelWrapper.prototype.render = function() {
return this.props.child;
};
TopLevelWrapper.isReactTopLevelWrapper = true;
*/
// 可以看出TopLevelWrapper代码就是一个简单的ReactComponent,类似于 extend React.Component, 并重写了方法render
var nextContext;
// 如果存在父组件,即不是顶级组件的情况下(在ReactDOM.render时,parentComponent为null)
if (parentComponent) {
var parentInst = ReactInstanceMap.get(parentComponent);
nextContext = parentInst._processChildContext(parentInst._context);
} else {
nextContext = emptyObject;
}
// 这时候preComponent = null;
var prevComponent = getTopLevelWrapperInContainer(container);
if (prevComponent) {
//
var prevWrappedElement = prevComponent._currentElement;
var prevElement = prevWrappedElement.props.child;
// diff 简单概括就是如果渲染的节点和原节点type和key(所以像listview可以通过设置key来进行优化)都不变的时候,直接更新就好,不用在去重新渲染一遍
if (shouldUpdateReactComponent(prevElement, nextElement)) {
var publicInst = prevComponent._renderedComponent.getPublicInstance();
var updatedCallback =
callback &&
function() {
callback.call(publicInst);
};
ReactMount._updateRootComponent(
prevComponent,
nextWrappedElement,
nextContext,
container,
updatedCallback,
);
return publicInst;
} else {
//否则的话卸载掉该容器的组件
ReactMount.unmountComponentAtNode(container);
}
}
// 获取container的跟元素
var reactRootElement = getReactRootElementInContainer(container);
// 确定container是否被markup,即添加了data-reactid,第一次渲染肯定是false
var containerHasReactMarkup =
reactRootElement && !!internalGetID(reactRootElement);
// 目前为false,因为ReactDOM.render调用时还没有实例化任何组件
var containerHasNonRootReactChild = hasNonRootReactChild(container);
// 目前为false
var shouldReuseMarkup =
containerHasReactMarkup &&
!prevComponent &&
!containerHasNonRootReactChild;
// 关键代码,渲染,插入都在这里面
var component = ReactMount._renderNewRootComponent(
nextWrappedElement,
container,
shouldReuseMarkup,
nextContext,
)._renderedComponent.getPublicInstance();
if (callback) {
callback.call(component);
}
return component;
}
根据代码我们知道,在_renderSubtreeIntoContainer方法的时候,他会优先判断渲染节点和原节点的type和key是否一致,如果一致,直接调用_updateRootComponent更新,否则才会去重新render新的组件,因此在渲染listview等需要大量刷新的组件时,可以通过设置key去优化显示,减少重新渲染
在看看_renderNewRootComponent的代码
/**
* Render a new component into the DOM. Hooked by hooks!
*
* @param {ReactElement} nextElement 即将渲染的组件
* @param {DOMElement} container 容器元素
* @param {boolean} shouldReuseMarkup 是否需要重新标记元素
* @return {ReactComponent} nextComponent 返回一个ReactComponent
*/
_renderNewRootComponent: function(
nextElement,
container,
shouldReuseMarkup,
context,
) {
//主要和滚动条有关,目前不需要太关心
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
//实例化React Component,
var componentInstance = instantiateReactComponent(nextElement, false);
/*
上文instantiateReactComponent
function instantiateReactComponent(node, shouldHaveDebugID) {
var instance;
if (node === null || node === false) {
// 如果是空对象
instance = ReactEmptyComponent.create(instantiateReactComponent);
} else if (typeof node === 'object') {
// 如果是Node,(包括dom节点以及reactElement)
var element = node;
// 原生对象
if (typeof element.type === 'string') {
instance = ReactHostComponent.createInternalComponent(element);
} else {
// react组件
instance = new ReactCompositeComponentWrapper(element);
}
//如果元素本来就是一个string或者number,如 111中的111
} else if (typeof node === 'string' || typeof node === 'number') {
//创建一个
instance = ReactHostComponent.createInstanceForText(node);
}
//这两个参数用于dom 和 art diff算法
instance._mountIndex = 0;
instance._mountImage = null;
return instance;
}
*/
/* 批量更新方法,具体实现可以见 ReactDefaultBatchingStrategy.js中 batchedUpdate方法,实际就是执行
* batchedMountComponentIntoNode方法,将后面的参数传入batchedMountComponentIntoNode中
*/
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
container,
shouldReuseMarkup,
context,
);
var wrapperID = componentInstance._instance.rootID;
instancesByReactRootID[wrapperID] = componentInstance;
return componentInstance;
},
batchedMountComponentIntoNode中,使用了React中大量使用的事务机制,调用了mountComponentIntoNode方法,事务机制后面有空在来研究,我们直接看mountComponentIntoNode
function mountComponentIntoNode(
wrapperInstance,
container,
transaction,
shouldReuseMarkup,
context,
) {
/*调用刚才ReactComponent instance中mountComponent方法,将React组件解析成对应的html(对应不同ReactComponent instance)mountComponent也是不同的
hello react, 对应的是ReactDOMTextComponent,最终解析成的HTML为hello react
*/
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,
);
}
看看ReactReconciler中的mountComponent代码
mountComponent: function(
internalInstance,
transaction,
hostParent,
hostContainerInfo,
context,
parentDebugID, // 0 in production and for roots
) {
var markup = internalInstance.mountComponent(
transaction,
hostParent,
hostContainerInfo,
context,
parentDebugID,
);
if (
internalInstance._currentElement &&
internalInstance._currentElement.ref != null
) {
transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
}
return markup;
},
由代码可以知道,其实本质也是调用了ReactComponent实例的mountComponent,刚才我们说到,不同的ReactComponent实例会有不同的mountComponent方法
大体有这三种实例:
- ReactHostComponent.createInternalComponent(element); 原生组件
- new ReactCompositeComponentWrapper(element); 自定义React组件
- ReactHostComponent.createInstanceForText(node); 字符串/数字
我们看一下最复杂的自定义React组件的mountComponent方法
mountComponent: function(
transaction,
hostParent,
hostContainerInfo,
context,
) {
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 a
var doConstruct = shouldConstruct(Component);
// 本质上就是new 了一个React.createClass(), 即Component组件,代码可以自己看看,比较简单
var inst = this._constructComponent(
doConstruct,
publicProps,
publicContext,
updateQueue,
);
var renderedElement;
// 当不存在构造函数,并且没有new出来的组件实例是null或者组件实例没有render方法,那么可以认为这是一个无状态组件
if (!doConstruct && (inst == null || inst.render == null)) {
renderedElement = inst;
warnIfInvalidElement(Component, renderedElement);
// new 一个无状态组件
inst = new StatelessComponent(Component);
this._compositeType = CompositeTypes.StatelessFunctional;
} else {
if (isPureComponent(Component)) {
this._compositeType = CompositeTypes.PureClass;
} else {
this._compositeType = CompositeTypes.ImpureClass;
}
}
// 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);
var initialState = inst.state;
if (initialState === undefined) {
inst.state = initialState = null;
}
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, // 感谢钟佳锋同学的指正,这里的renderedElement是后面_renderValidatedComponent中调用内部的render方法获取
hostParent,
hostContainerInfo,
transaction,
context,
);
}
if (inst.componentDidMount) { //生命周期componentDidMount
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
return markup;
},
从上面我们知道自定义React组件真正实例化(new)的时候就在mountComponment之中,但是这只是简单的实例化,返回值html是在performInitialMount中处理的,在看看performInitialMount中的代码
performInitialMount: function(
renderedElement,
hostParent,
hostContainerInfo,
transaction,
context,
) {
var inst = this._instance;
var debugID = 0;
if (inst.componentWillMount) {
//该实体如果有componentWillMount方法,生命周期之一
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
// 获取子组件,实际是调用React.createClass中的render方法,因为render方法会返回一个ReactElement对象
if (renderedElement === undefined) {
renderedElement = this._renderValidatedComponent();
}
var nodeType = ReactNodeTypes.getType(renderedElement);
this._renderedNodeType = nodeType;
//再次调用instantiateReactComponent初始化
var child = this._instantiateReactComponent(
renderedElement,
nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */,
);
this._renderedComponent = child;
//再一次调用mountComponent
var markup = ReactReconciler.mountComponent(
child,
transaction,
hostParent,
hostContainerInfo,
this._processChildContext(context),
debugID,
);
return markup;
},
在这段代码中,renderedElement被_renderValidatedComponent重新进行了赋值,根据后面child这个变量命名,我们猜测,这个方法会返回子组件,最后,又在一次调用了ReactReconciler.mountComponent方法,和mountComponentIntoNode方法调用手段一致,只是第一个参数变成了child,简单猜测一下,这个时候在渲染子组件。为了证实猜测,在来看看_renderValidatedComponent方法
_renderValidatedComponent: function() {
var renderedElement;
renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
return renderedElement;
},
对应的_renderValidatedComponentWithoutOwnerOrContext方法
/**
* @protected
*/
_renderValidatedComponentWithoutOwnerOrContext: function() {
var inst = this._instance;
var renderedElement;
//回想一下我们React.createClass中render方法里面被babel解析后是什么
//render中{return React.createElement}
//所以他又是一个ReactElement
renderedElement = inst.render();
return renderedElement;
},
果然,是调用了render方法,也就是render中的子组件。随后组件又调用了_instantiateReactComponentfan方法初始化最后再一次mountComponent去实例化子组件,形成递归去渲染,直到得到最后的markup,同时不难想象,随着不断的递归只有下面的2类会返回正常的mask对象
- ReactHostComponent.createInternalComponent(element); 原生组件
- ReactHostComponent.createInstanceForText(node); 字符串/数字
我们在来看看createInternalComponent方法,该方法会new一个ReactDOMComponent(指代原生组件),我们来看看ReactDOMComponent的mountComponent有什么
mountComponent: function(
transaction,
hostParent,
hostContainerInfo,
context,
) {
this._rootNodeID = globalIdCounter++;
this._domID = hostContainerInfo._idCounter++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;
var props = this._currentElement.props;
//针对不同的element,进行不同的处理,在构造函数中this._tag=element.tag.toLowCase()
switch (this._tag) {
case 'audio':
case 'form':
case 'iframe':
case 'img':
case 'link':
case 'object':
case 'source':
case 'video':
this._wrapperState = {
listeners: null,
};
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'input':
ReactDOMInput.mountWrapper(this, props, hostParent);
props = ReactDOMInput.getHostProps(this, props);
transaction.getReactMountReady().enqueue(trackInputValue, this);
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'option':
ReactDOMOption.mountWrapper(this, props, hostParent);
props = ReactDOMOption.getHostProps(this, props);
break;
case 'select':
ReactDOMSelect.mountWrapper(this, props, hostParent);
props = ReactDOMSelect.getHostProps(this, props);
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
case 'textarea':
ReactDOMTextarea.mountWrapper(this, props, hostParent);
props = ReactDOMTextarea.getHostProps(this, props);
transaction.getReactMountReady().enqueue(trackInputValue, this);
transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
break;
}
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;
var mountImage;
// 调用render时,useCreateElement为true
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 + '>';
}
}
switch (this._tag) {
case 'input':
transaction.getReactMountReady().enqueue(inputPostMount, this);
if (props.autoFocus) {
transaction
.getReactMountReady()
.enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'textarea':
transaction.getReactMountReady().enqueue(textareaPostMount, this);
if (props.autoFocus) {
transaction
.getReactMountReady()
.enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'select':
if (props.autoFocus) {
transaction
.getReactMountReady()
.enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'button':
if (props.autoFocus) {
transaction
.getReactMountReady()
.enqueue(AutoFocusUtils.focusDOMComponent, this);
}
break;
case 'option':
transaction.getReactMountReady().enqueue(optionPostMount, this);
break;
}
return mountImage;
}
由代码可以知道react通过type对input,select,script等特殊元素进行单独的处理,最后调用createElement来生成一个普通的元素,同时调用了this._createInitialChildren(transaction, props, context, lazyTree)方法去实例化其中的子组件,同原生组件一下,形成递归返回到tree中,最后mountImage = lazyTree;拿到生成的节点,这里就不在看原生组件_createInitialChildren的实现了
回到开mountCompouentIntoNode的代码,最后调用了ReactMount._mountImageIntoNode
方法,这个方法就是真真的把节点插入带容器中去了,看一下实现;
_mountImageIntoNode: function(
markup,
container,
instance,
shouldReuseMarkup,
transaction,
) {
//暂时不用关心
if (shouldReuseMarkup) {
......
}
//render的时候该值为true
if (transaction.useCreateElement) {
//移除container中的节点
while (container.lastChild) {
container.removeChild(container.lastChild);
}
DOMLazyTree.insertTreeBefore(container, markup, null);
} else {
setInnerHTML(container, markup);
ReactDOMComponentTree.precacheNode(instance, container.firstChild);
}
},
最后实际上是调用DOMLazyTree.insertTreeBefore完成节点插入,这一段代码很简单,就不在单独分析了
总结
终于走完了React整个渲染流程,我们可以发现React抽象程度非常的高,源码读起来特别的复杂,这里也只是简单的阅读整个流程,细节太多,没有深究