勉強がてら、どのように作られているのか興味があるので、少しだけ見てみる。
React.js
var ReactIsomorphic = require('ReactIsomorphic');
var assign = require('Object.assign');
assign(React, ReactIsomorphic);
Object.assignでReactIsomorphicのプロパティがコピーされているので、ReactIsomorphicを見に行く。
ReactIsomorphic.js
見てみると、色々なモジュールの機能をimportしているようなので、試しによく使うcreateClassを見に行く。
var ReactClass = require('ReactClass');
var React = {
...,
createClass: ReactClass.createClass,
...,
};
ReactClassに定義されているものを使用しているようなので、ReactClassを見に行く。
ReactClass.js
[URL](https://github.com/facebook/react/blob/master/src/isomorphic/classic/class/ReactClass.js]
まずはどれがexportされているかを確認する。
module.exports = ReactClass;
ReactClassを見に行く。
var ReactClass = {
...,
createClass: function(spec) {
...,
}
};
createClassを発見したので見ていく。
createClass: function(spec) {
...,
return Constructor;
}
どうやらConstructorを返しているだけらしい。createClassだから当然とも言える。
Constructorを見てみる。
var ReactClass = {
// thisのscopeはReactClass
var Constructor = function(props, context, updater) {
...,
// Wire up auto-binding
if (this.__reactAutoBindPairs.length) {
// __reactAutoBindPairsにあるmethodをConstrucotrにbindする
bindAutoBindMethods(this);
}
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
...,
this.state = initialState;
};
...,
// prototypeとconstructorを設定
Constructor.prototype = new ReactClassComponent();
Constructor.prototype.constructor = Constructor;
// ここからConstructorにpropertyを追加
Constructor.prototype.__reactAutoBindPairs = [];
injectedMixins.forEach(
mixSpecIntoComponent.bind(null, Constructor)
);
mixSpecIntoComponent(Constructor, spec);
if (Constructor.getDefaultProps) {
Constructor.defaultProps = Constructor.getDefaultProps();
}
// renderは必須
invariant(
Constructor.prototype.render,
'createClass(...): Class specification must implement a `render` method.'
);
// Reduce time spent doing lookups by setting these on the prototype.
for (var methodName in ReactClassInterface) {
if (!Constructor.prototype[methodName]) {
Constructor.prototype[methodName] = null;
}
}
};
Constructor自体はReactClassに初期状態をセットしているだけのようだ。
prototypeにReactClassComponentを設定後、プロパティを追加して最終的にclassが出来上がる。
まずは、ReactClassComponentを見に行く。
var ReactClassComponent = function() {};
assign(
ReactClassComponent.prototype,
ReactComponent.prototype,
ReactClassMixin
);
prototypeにReactComponent.prototypeとReactClassMixinがコピーされているようだ。
この2つを見てみる。
var ReactComponent = require('ReactComponent');
var ReactClassMixin = {
replaceState: function(newState, callback) {
// stateをupdateする
// deprecatedなので気にする必要はない?
},
isMounted: function() {
return this.updater.isMounted(this);
},
};
ReactClassMixinはMixinという名のとおり、Mixinされるclassがもっているであろうupdaterというプロパティを使用する。
Pythonをそこそこ触ったことがある人ならMixin的なやつは使ったことがある・・はず。
ReactComponentは別モジュールとなっているので見に行く。
ReactComponent.js
function ReactComponent(props, context, updater) {
// constructorはReactClassでは使用されない
}
ReactComponent.prototype.isReactComponent = {};
ReactComponent.prototype.setState = function(partialState, callback) {
// stateはobjectかfunctionかnull
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.'
);
// state変更、上述したreplaceStateはこれがあるから使われないのかな
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback);
}
}
// setStateからassertをなくしたもの
ReactComponent.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this);
if (callback) {
this.updater.enqueueCallback(this, callback);
}
};
setStateなどのmethodが定義されている。
次に一番重要と思われる、spec(React.createClassの引数)とConstructorのひも付けを行うmixSpecIntoComponentを見ていく。
function mixSpecIntoComponent(Constructor, spec) {
// specが指定されなければ何もしない
// specがないとrenderがないのでエラーのはず
if (!spec) {
return;
}
// specはobjectのみ
invariant(
typeof spec !== 'function',
'ReactClass: You\'re attempting to ' +
'use a component class or function as a mixin. Instead, just use a ' +
'regular object.'
);
// specはReactElementにあてはまらないobjectのみ
invariant(
!ReactElement.isValidElement(spec),
'ReactClass: You\'re attempting to ' +
'use a component as a mixin. Instead, just use a regular object.'
);
// Constructorのprototypeからbindするmethodをコピー
var proto = Constructor.prototype;
var autoBindPairs = proto.__reactAutoBindPairs;
// specが{mixins: ...}を持つ場合はmixinsのobjectをspecとして再帰的にこのfunctionを実行する
if (spec.hasOwnProperty(MIXINS_KEY)) {
RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
}
for (var name in spec) {
// 他のprototypeから継承したspecのpropertyは無視
if (!spec.hasOwnProperty(name)) {
continue;
}
// mixinsは事前に処理しているので無視
if (name === MIXINS_KEY) {
// We have already handled mixins in a special case above.
continue;
}
var property = spec[name];
// Constructorがすでにどこかで同じpropertyも持っているか確認
var isAlreadyDefined = proto.hasOwnProperty(name);
// Constructorのものをspecのものでoverrideしていいかを確認
validateMethodOverride(isAlreadyDefined, name);
// 予約されているpropertyの場合はspecのものをコピーする
if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
RESERVED_SPEC_KEYS[name](Constructor, property);
} else {
// Setup methods on prototype:
// The following member methods should not be automatically bound:
// 1. Expected ReactClass methods (in the "interface").
// 2. Overridden methods (that were mixed in).
var isReactClassMethod =
ReactClassInterface.hasOwnProperty(name);
var isFunction = typeof property === 'function';
// あらかじめ定義されているproperty名かを確認
// specにautobindというpropertyがないとfalseなので明示的に指定して使う感じかな?
var shouldAutoBind =
isFunction &&
!isReactClassMethod &&
!isAlreadyDefined &&
spec.autobind !== false;
if (shouldAutoBind) {
autoBindPairs.push(name, property);
proto[name] = property;
// おそらく大抵はこちら
} else {
// Constructorに定義されているpropertyの場合
// setStateなどの場合だが良く使うのだろうか?
if (isAlreadyDefined) {
var specPolicy = ReactClassInterface[name];
// These cases should already be caught by validateMethodOverride.
invariant(
isReactClassMethod && (
specPolicy === SpecPolicy.DEFINE_MANY_MERGED ||
specPolicy === SpecPolicy.DEFINE_MANY
),
'ReactClass: Unexpected spec policy %s for key %s ' +
'when mixing in component specs.',
specPolicy,
name
);
// For methods which are defined more than once, call the existing
// methods before calling the new property, merging if appropriate.
// property名でmergeするかchainするかを決める
if (specPolicy === SpecPolicy.DEFINE_MANY_MERGED) {
// 2つのfunctionの結果を1つの連想配列にkey-valueとして保存するfunctionを返す
proto[name] = createMergedResultFunction(proto[name], property);
} else if (specPolicy === SpecPolicy.DEFINE_MANY) {
// functionを呼び出すだけで結果は無視するfunctionを返す
proto[name] = createChainedFunction(proto[name], property);
}
// 定義されていなければ素直に設定する
} else {
proto[name] = property;
if (__DEV__) {
// Add verbose displayName to the function, which helps when looking
// at profiling tools.
if (typeof property === 'function' && spec.displayName) {
proto[name].displayName = spec.displayName + '_' + name;
}
}
}
}
}
}
}
予約や定義されているもの以外はspecのpropertyが設定されるという単純な構造でした。
予約や定義されているものによっては返り値に関心があるものとないものがあるので、そこを設定するときは注意が必要という感じでしょうか。
以上でReact.createClassの仕組みはだいたいわかりました。
あとは個人的に気になるupdaterを見ていきます。
ReactNoopUpdateQueue.js
見てみると宣言のみで実装はないみたいですね。
JSXと関係が強そうなので、次回はreact-dom, jsx周りを見てみたいですね。