Reactの内部を見てみる~ReactDOM編~
ReactDOM.js
var React = {
findDOMNode: findDOMNode,
render: render,
unmountComponentAtNode: ReactMount.unmountComponentAtNode,
version: ReactVersion,
/* eslint-disable camelcase */
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer,
/* eslint-enable camelcase */
};
本体はここのようだ。
Tutorialでお世話になったrenderを追っていく。
ReactDOM.render
var ReactMount = require('ReactMount');
var ReactPerf = require('ReactPerf');
var render = ReactPerf.measure('React', 'render', ReactMount.render);
ReactPerf.measureの返り値なのでReactPerfを見てみる。
var ReactPerf = {
...,
measure: function(objName, fnName, func) {
...,
return func;
}
};
productionではfuncをそのまま返しているだけなので、実態はReactMountにあるようだ。
var ReactMount = {
...,
_renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
...,
var component = ReactMount._renderNewRootComponent(
nextWrappedElement,
container,
shouldReuseMarkup,
parentComponent != null ?
parentComponent._reactInternalInstance._processChildContext(
parentComponent._reactInternalInstance._context
) :
emptyObject
)._renderedComponent.getPublicInstance();
if (callback) {
callback.call(component);
}
return component;
},
...,
render: function(nextElement, container, callback) {
return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
}
}
containerは描画するelementの外側のDOMとなる。
最終的には新しいcomponentを返すだけ。しかし、それまでにかなりのエラー判定などがある。。(ここでは掘り下げないようにしよう・・)
componentの作成を見に行く。
...,
_renderNewRootComponent: function(
nextElement,
container,
shouldReuseMarkup,
context
) {
...,
var componentInstance = instantiateReactComponent(nextElement);
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
container,
shouldReuseMarkup,
context
);
...,
return componentInstance;
},
componentInstanceを作成して、何かのupdateをしてcomponentInstanceを返している。
function instantiateReactComponent(node) {
var instance;
if (node === null || node === false) {
instance = ReactEmptyComponent.create(instantiateReactComponent);
} else if (typeof node === 'object') {
var element = node;
// Special case string values
if (typeof element.type === 'string') {
instance = ReactNativeComponent.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);
} else {
instance = new ReactCompositeComponentWrapper(element);
}
} else if (typeof node === 'string' || typeof node === 'number') {
instance = ReactNativeComponent.createInstanceForText(node);
} else {
// エラー処理
}
...,
return instance;
}
nextElementが空だったら空のcomponentを返しているようだ。
あとはnodeのtypeによって作成するcomponentの種類を変えている。
一つずつ見てみる。
var ReactEmptyComponent = {
create: function(instantiate) {
return emptyComponentFactory(instantiate);
},
};
nextElementが空の場合。
FactoryがinstantiateReactComponentを引数にして何かを実行するfunctionを返す。
var genericComponentClass = null;
var textComponentClass = null;
function createInternalComponent(element) {
invariant(
genericComponentClass,
'There is no registered component for the tag %s',
element.type
);
return new genericComponentClass(element);
}
function createInstanceForText(text) {
return new textComponentClass(text);
}
var ReactNativeComponent = {
getComponentClassForElement: getComponentClassForElement,
createInternalComponent: createInternalComponent,
createInstanceForText: createInstanceForText,
isTextComponent: isTextComponent,
injection: ReactNativeComponentInjection,
};
nextElementが{ type: ..., }と{...}とstring,numberの場合。
この時点ではどちらもnullなのでどういうことか不明。とは言ってもelementはJSXで表現されているので、JSXで何かをしている可能性がある。
なお、typeがReactで定義されているfunctionであればそれを適用するようだ。
var ReactCompositeComponentWrapper = function(element) {
this.construct(element);
};
assign(
ReactCompositeComponentWrapper.prototype,
ReactCompositeComponent.Mixin,
{
_instantiateReactComponent: instantiateReactComponent,
}
);
ReactCompositeComponent.Mixinに実態がある。見てみる。
var ReactCompositeComponentMixin = {
construct: function(element) {
this._currentElement = element;
this._rootNodeID = null;
this._instance = null;
this._nativeParent = null;
this._nativeContainerInfo = null;
// See ReactUpdateQueue
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;
},
...,
};
var ReactCompositeComponent = {
Mixin: ReactCompositeComponentMixin,
};
currentElementにnextElementを持つobjectを作成するようだ。
次にupdateしている処理を見に行く。
function batchedUpdates(callback, a, b, c, d, e) {
ensureInjected();
batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}
var ReactUpdates = {
/**
* React references `ReactReconcileTransaction` using this property in order
* to allow dependency injection.
*
* @internal
*/
ReactReconcileTransaction: null,
batchedUpdates: batchedUpdates,
enqueueUpdate: enqueueUpdate,
flushBatchedUpdates: flushBatchedUpdates,
injection: ReactUpdatesInjection,
asap: asap,
};
この時点ではensureInjected()でエラーが出る。どういうことだ。。injectするところがあるか探す。
おそらくnextElementがJSXであるところにヒントがあるように思える。
JSXをまずは解明しないと無理そうだ。。