一、物料准备

1.克隆react源码, github 地址:https://github.com/facebook/react.git

2.安装gulp

3.在react源码根目录下:

$npm install

$gulp default

(建议使用node 6.0+)

gulp将文件处理在根目录下的build文件夹中,打开build查看react的源码,结构清晰,引用路径明了

二、从生成 virtual dom 开始

react 生成一个组件有多种写法:

es 5下:var Cp=React.createClass({...})

es 6下:class Cp extends React.Component{...}

下面打开./build/node_modules/react/lib 文件夹,找到React.js 可以看到如下关键代码:

var React = {

  // Modern

  Children: {
map: ReactChildren.map,
forEach: ReactChildren.forEach,
count: ReactChildren.count,
toArray: ReactChildren.toArray,
only: onlyChild
}, Component: ReactComponent,
PureComponent: ReactPureComponent, createElement: createElement,
cloneElement: cloneElement,
isValidElement: ReactElement.isValidElement, // Classic PropTypes: ReactPropTypes,
createClass: ReactClass.createClass,
createFactory: createFactory,
createMixin: function (mixin) {
// Currently a noop. Will be used to validate and trace mixins.
return mixin;
}, // This looks DOM specific but these are actually isomorphic helpers
// since they are just generating DOM strings.
DOM: ReactDOMFactories, version: ReactVersion, // Deprecated hook for JSX spread, don't use this for anything.
__spread: __spread
};

由此得知:React.createClass => ReactClass.createClass

React.component => ReactComponent

1.ReactClass.createClass

下面还是在当前的目录下寻找ReactClass.js文件,查看到如下关键代码段:

var ReactClass = {
createClass: function (spec) {
var Constructor = function (props, context, updater) {
//如果不是生产环境 输出信息类警告 目前忽略
if (process.env.NODE_ENV !== 'production') {...}
// 自动绑定相关方法 目前忽略
if (this.__reactAutoBindPairs.length) {...}
//为组件绑定props context refs updater属性
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
//初始组件state为null
this.state = null;
//如果有getInitialState则执行
var initialState = this.getInitialState ? this.getInitialState() : null;
//在非生产环境下为配合mock 设置initialState为null 目前忽略
if (process.env.NODE_ENV !== 'production') {...}
//其他情况下的兼容性处理,目前忽略
...
//将初始化的state赋值给组件state
this.state = initialState;
};
//设置Constructor的原型
Constructor.prototype = new ReactClassComponent();
Constructor.prototype.constructor = Constructor;
Constructor.prototype.__reactAutoBindPairs = [];
//合并研发同学写入的createClass({中的东西})
mixSpecIntoComponent(Constructor, spec);
//如果存在getDefaultProps则执行
if (Constructor.getDefaultProps) {
Constructor.defaultProps = Constructor.getDefaultProps();
}
...省略一些无关主逻辑的操作 return Constructor;
} };

通过上面的代码我们可以知道:

a.createClass生成一个constructor并return它,这个constructor就是我们的组件
b.这个constructor继承自ReactClassComponent
c.了解react组件声明周期的同学应该知道React组件在整个生命周期中getDefaultProps只执行一次了吧
d.研发组件的同学在createClass({中写的东西})是通过mixSpecIntoComponent方法融合进constructor中的

下面请看mixSpecIntoComponent代码

function mixSpecIntoComponent(Constructor, spec) {
if (!spec) {
//当spec不存在时 即研发同学没有写createClass中的东西
...省略警告文本
return;
}
...省略spec类型容错处理
var proto = Constructor.prototype;
var autoBindPairs = proto.__reactAutoBindPairs;
//关于mixins的相关处理 其实就是递归调用mixSpecIntoComponent
//MIXINS_KEY="mixins"
if (spec.hasOwnProperty(MIXINS_KEY)) {
RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
}
//循环遍历spec
for (var name in spec) {
...省略容错处理
var property = spec[name];
var isAlreadyDefined = proto.hasOwnProperty(name);
//覆写constructor.prototype中的方法
validateMethodOverride(isAlreadyDefined, name);
//对特定的属性名做特殊处理
if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
RESERVED_SPEC_KEYS[name](Constructor, property);
} else {
...省略特殊处理
if (shouldAutoBind) {
...省略自动绑定相关处理
} else {
if (isAlreadyDefined) {
...省略已定义容错处理
} else {
//关键点 将property赋值给Contructor
proto[name] = property; }
}
}
}
}

通过以上代码就可以大致了解其工作原理了

而ReactClassComponent函数生成代码如下:

var ReactClassComponent = function () {};
_assign(ReactClassComponent.prototype, ReactComponent.prototype, ReactClassMixin);

它的原型是由ReactComponent.prototype及ReactClassMixin复合而成(_assing在根目录 node_modules/fbjs目录下,为facebook工具库中封装的函数,相当于es6 的 Object.assign)

ReactClassMixin源码如下:

var ReactClassMixin = {
replaceState: function (newState, callback) {
this.updater.enqueueReplaceState(this, newState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'replaceState');
}
},
isMounted: function () {
return this.updater.isMounted(this);
}
};

定义了 replaceState及 isMounted两个方法

至于ReactComponent在./ReactComponent.js文件中,prototype源码如下

ReactComponent.prototype.isReactComponent = {};

//setState方法
ReactComponent.prototype.setState = function (partialState, callback) {
...省略报警信息
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
}; ReactComponent.prototype.forceUpdate = function (callback) {
this.updater.enqueueForceUpdate(this);
if (callback) {
this.updater.enqueueCallback(this, callback, 'forceUpdate');
}
};

2.ReactComponent

ReactComponent的原型请参见上面的代码,其构造函数如下

function ReactComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}

对于extends 关键字的使用,可以参看babel上对于extends的转换,以了解其运行机制
简单点说,extends转换成ES5有以下两个步骤:

1.Object.create方法去生成对象,即:Cp.prototype=Object.create(ReactComponent.prototpe,{配置对象}) 实现原型继承的目的

2.通过ReactComponent.apply(this,arguments)的方法实现构造函数的继承

实际转换加上属性的验证十分繁杂,有兴趣的同学请亲自实践

这种通过extends方式生成的组件,没有createClass中对getInitialState及getDefaultProps的显示管理

需要研发同学在constructor中进行处理,至于其背后有何机制,以后再做讨论

三、将jsx object变成 DOMComponent

在react中,组件是用jsx语法书写的,jsx语法在编译成正常js语法时,早期使用的是react官方自身的JSTransform,后来因为其功能与babel jsx编译器功能重复,所以被官方抛弃,现今使用第三方的babel作为jsx编译器。jsx语法编译不在本文范畴之内。

不过通过编码实践以及编译后文件查看我们可以得知,jsx语法的组件被编译器编译成如下格式js语句:

_react2.default.createElement(
"div",
{ className: "bookmenu" },
_react2.default.createElement(_Header2.default, { pageTitle: "xxx" }),
_react2.default.createElement(_Title2.default, { title: "xxx" })
);

这其中由于使用ES6 import的缘故,引入模块时会为模块自动添加default作为输出,所以_react2.default其实就是React对象,而在ES5下,则相对清晰:

我们用babel编译器编译jsx文件结果如下:

var HelloBox = React.createClass({
render: function () {
return React.createElement(
"div",
{ className: "someClass" },
"hello world"
);
}
});
ReactDOM.render(React.createElement(HelloBox, null), document.getElementById("app"));

由此可知一个组件的实质是React.createElement方法返回的内容,下面我们将追寻源码中的createElement的调用栈

在react源码中引用的createElement其实是 var createElement = ReactElement.createElement;

找到ReactElement文件:

//示例:React.createElement("div",{ className: "name" },"hello world");
ReactElement.createElement = function (type, config, children) {
var propName; // 属性初始化
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
//如果配置对象存在则验证并赋值给属性
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
} self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
}
//获得组件的children,并缓存childArray数组中
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (process.env.NODE_ENV !== 'production') {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
} // 设置props
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
//省略非生产环境下的配置
//返回ReactElement函数的返回值
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};

我们追踪到实际的返回值是ReactElement的执行结果,继续:

ReactElement函数如下:

var ReactElement = function (type, key, ref, self, source, owner, props) {
var element = {
// 保存react node type
$$typeof: REACT_ELEMENT_TYPE,
// dom type
type: type,
key: key,
ref: ref,
props: props,
// 记录负责创建该元素的组件.
_owner: owner
};
//去除非生产环境的配置
//返回这个element
return element;
};

由此我们得知组件的实质是一个结构大致如下的Object

var element = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
};

数据类型大致了解,而将组件变成浏览器可预览的dom元素需要使用ReactDOM.render方法

下面就来寻找render方法的实质

在之前提到的build文件夹下的react-dom/lib目录下可以找到ReactDOM.js一窥究竟:

var ReactDOM = {
findDOMNode: findDOMNode,
render: ReactMount.render,
unmountComponentAtNode: ReactMount.unmountComponentAtNode,
version: ReactVersion, /* eslint-disable camelcase */
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer
};

原来render方法是ReactMount.render方法的引用,还是在react-dom目录下,找到ReactMount.js

ReactMount关键源码如下:

var ReactMount = {
//调用顺序 3
_renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
//此步骤跳向instantiateReactComponent
var componentInstance = instantiateReactComponent(nextElement, false);
//批量更新 后面会提到
ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);
//为dom节点添加相关ID
var wrapperID = componentInstance._instance.rootID;
instancesByReactRootID[wrapperID] = componentInstance;
//返回已经成为能够被浏览器识别的dom节点
return componentInstance;
}, //调用顺序 2
_renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) {
//省略生产环境的适配及相关处理
var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)._renderedComponent.getPublicInstance();
if (callback) {
callback.call(component);
}
return component;
},
//调用顺序 1
//并没有做什么,直接调用ReactMount._renderSubtreeIntoContainer
render: function (nextElement, container, callback) {
return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
}, };

在步骤3的时候又转入到instantiateReactComponent中去处理,这里是将对象转变为DOMComponent的关键所在

function instantiateReactComponent(node, shouldHaveDebugID) {
var instance; if (node === null || node === false) {
//如果传入的对象为空,则创建空的节点
instance = ReactEmptyComponent.create(instantiateReactComponent);
} else if (typeof node === 'object') {
var element = node;
//去掉生产环境相关检测
// 大多数情况下element.type都会是字符串,因此重点查看此内容
if (typeof element.type === 'string') {
instance = ReactHostComponent.createInternalComponent(element);
} else if (isInternalComponentType(element.type)) {
//如果element.type为函数且prototype不为undefined
instance = new element.type(element);
if (!instance.getHostNode) {
instance.getHostNode = instance.getNativeNode;
}
} else {
//以上两种情况都不是
instance = new ReactCompositeComponentWrapper(element);
}
} else if (typeof node === 'string' || typeof node === 'number') {
//如果是纯文字则创建文本节点
instance = ReactHostComponent.createInstanceForText(node);
} else {
//忽略兼容性处理 大致是不进行任何操作
} // 用于diff操作的两个属性
instance._mountIndex = 0;
instance._mountImage = null; return instance;
}
//针对element.type既不是函数也不是字符串,则使用ReactCompositeComponent去生成组件
var ReactCompositeComponentWrapper = function (element) {
this.construct(element);
};
_assign(ReactCompositeComponentWrapper.prototype, ReactCompositeComponent, {
_instantiateReactComponent: instantiateReactComponent
}); //ReactCompositeComponent源码如下
var ReactCompositeComponent = {
//提供construct,以element为参数,为生成的对象附加各种属性
construct: function (element) {
this._currentElement = element;
this._rootNodeID = 0;
this._compositeType = null;
this._instance = null;
this._hostParent = null;
this._hostContainerInfo = null; // See ReactUpdateQueue
this._updateBatchNumber = null;
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; // ComponentWillUnmount shall only be called once
this._calledComponentWillUnmount = false; if (process.env.NODE_ENV !== 'production') {
this._warnedAboutRefsInRender = false;
}
},

instantiateReactComponent中针对传入的element对象的不同做出不同的处理,关键核心的是调用:

ReactHostComponent.createInternalComponent

ReactHostComponent.createInstanceForText

这两个方法将会把element转化成DOMComponent对象,二者的源码如下:

var genericComponentClass=null;
var textComponentClass=null;
var ReactHostComponentInjection = {
//接收一个参数作为构造函数
injectGenericComponentClass: function (componentClass) {
genericComponentClass = componentClass;
},
//接收生成文本节点的构造函数
injectTextComponentClass: function (componentClass) {
textComponentClass = componentClass;
},
// This accepts a keyed object with classes as values. Each key represents a
// tag. That particular tag will use this class instead of the generic one.
injectComponentClasses: function (componentClasses) {
_assign(tagToComponentClass, componentClasses);
}
};
//生成dom节点
function createInternalComponent(element) {
//省略对genericComponentClass在特殊情况下的验证
//返回由genericComponentClass构造的节点
return new genericComponentClass(element);
}
//生成文本节点
function createInstanceForText(text) {
return new textComponentClass(text);
}
//检测是否为文本节点
function isTextComponent(component) {
return component instanceof textComponentClass;
}

看到这里整个逻辑似乎是断掉了,两个构造函数都是null,那么它们是如何生成React DOMComponent节点的呢

这还要从ReactDOM.js说起

var ReactDefaultInjection = require('./ReactDefaultInjection');
//执行inject
ReactDefaultInjection.inject(); var ReactDOM = {...};

在ReactDOM文件的开始位置引入了ReactDefaultInjection模块,并执行了它的inject方法

var ReactDOMComponentTree = require('./ReactDOMComponentTree');
var ReactDOMTextComponent = require('./ReactDOMTextComponent');
var ReactInjection = require('./ReactInjection'); function inject() {
.....
ReactInjection.HostComponent.injectGenericComponentClass(ReactDOMComponent); ReactInjection.HostComponent.injectTextComponentClass(ReactDOMTextComponent); .....
} //ReactInjection模块简版代码如下: var ReactHostComponent = require('./ReactHostComponent'); var ReactInjection = {
....
HostComponent: ReactHostComponent.injection
....
}; //ReactHostComponent.injection如下
var ReactHostComponentInjection = {
injectGenericComponentClass: function (componentClass) {
genericComponentClass = componentClass;
},
injectTextComponentClass: function (componentClass) {
textComponentClass = componentClass;
},
injectComponentClasses: function (componentClasses) {
_assign(tagToComponentClass, componentClasses);
}
};
var ReactHostComponent = {
createInternalComponent: createInternalComponent,
createInstanceForText: createInstanceForText,
isTextComponent: isTextComponent,
injection: ReactHostComponentInjection
};

现在我们通过上面的代码分析,node节点构造函数及text节点构造函数是由ReactDOMComponent、ReactDOMTextComponent这两个构造函数构造的

ReactDOMComponent源码如下:

function ReactDOMComponent(element) {
var tag = element.type;
validateDangerousTag(tag);
this._currentElement = element;
this._tag = tag.toLowerCase();
this._namespaceURI = null;
this._renderedChildren = null;
this._previousStyle = null;
this._previousStyleCopy = null;
this._hostNode = null;
this._hostParent = null;
this._rootNodeID = 0;
this._domID = 0;
this._hostContainerInfo = null;
this._wrapperState = null;
this._topLevelWrapper = null;
this._flags = 0;
if (process.env.NODE_ENV !== 'production') {
this._ancestorInfo = null;
setAndValidateContentChildDev.call(this, null);
}
} ReactDOMComponent.displayName = 'ReactDOMComponent'; ReactDOMComponent.Mixin = {
....
}; _assign(ReactDOMComponent.prototype, ReactDOMComponent.Mixin, ReactMultiChild);

ReactDomComponent的构造函数非常简单,同时原型为 ReactDOMComponent.Mixin 和 ReactMultiChild的复合产物

ReactDOMTextComponent的源码如下:

var ReactDOMTextComponent = function (text) {
// TODO: This is really a ReactText (ReactNode), not a ReactElement
this._currentElement = text;
this._stringText = '' + text;
// ReactDOMComponentTree uses these:
this._hostNode = null;
this._hostParent = null; // Properties
this._domID = 0;
this._mountIndex = 0;
this._closingComment = null;
this._commentNodes = null;
}; _assign(ReactDOMTextComponent.prototype, { /**
* Creates the markup for this text node. This node is not intended to have
* any features besides containing text content.
*
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @return {string} Markup for this text node.
* @internal
*/
mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
if (process.env.NODE_ENV !== 'production') {
var parentInfo;
if (hostParent != null) {
parentInfo = hostParent._ancestorInfo;
} else if (hostContainerInfo != null) {
parentInfo = hostContainerInfo._ancestorInfo;
}
if (parentInfo) {
// parentInfo should always be present except for the top-level
// component when server rendering
validateDOMNesting(null, this._stringText, this, parentInfo);
}
} var domID = hostContainerInfo._idCounter++;
var openingValue = ' react-text: ' + domID + ' ';
var closingValue = ' /react-text ';
this._domID = domID;
this._hostParent = hostParent;
if (transaction.useCreateElement) {
var ownerDocument = hostContainerInfo._ownerDocument;
var openingComment = ownerDocument.createComment(openingValue);
var closingComment = ownerDocument.createComment(closingValue);
var lazyTree = DOMLazyTree(ownerDocument.createDocumentFragment());
DOMLazyTree.queueChild(lazyTree, DOMLazyTree(openingComment));
if (this._stringText) {
DOMLazyTree.queueChild(lazyTree, DOMLazyTree(ownerDocument.createTextNode(this._stringText)));
}
DOMLazyTree.queueChild(lazyTree, DOMLazyTree(closingComment));
ReactDOMComponentTree.precacheNode(this, openingComment);
this._closingComment = closingComment;
return lazyTree;
} else {
var escapedText = escapeTextContentForBrowser(this._stringText); if (transaction.renderToStaticMarkup) {
// Normally we'd wrap this between comment nodes for the reasons stated
// above, but since this is a situation where React won't take over
// (static pages), we can simply return the text as it is.
return escapedText;
} return '<!--' + openingValue + '-->' + escapedText + '<!--' + closingValue + '-->';
}
}, /**
* Updates this component by updating the text content.
*
* @param {ReactText} nextText The next text content
* @param {ReactReconcileTransaction} transaction
* @internal
*/
receiveComponent: function (nextText, transaction) {
if (nextText !== this._currentElement) {
this._currentElement = nextText;
var nextStringText = '' + nextText;
if (nextStringText !== this._stringText) {
// TODO: Save this as pending props and use performUpdateIfNecessary
// and/or updateComponent to do the actual update for consistency with
// other component types?
this._stringText = nextStringText;
var commentNodes = this.getHostNode();
DOMChildrenOperations.replaceDelimitedText(commentNodes[0], commentNodes[1], nextStringText);
}
}
}, getHostNode: function () {
var hostNode = this._commentNodes;
if (hostNode) {
return hostNode;
}
if (!this._closingComment) {
var openingComment = ReactDOMComponentTree.getNodeFromInstance(this);
var node = openingComment.nextSibling;
while (true) {
!(node != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Missing closing comment for text component %s', this._domID) : _prodInvariant('67', this._domID) : void 0;
if (node.nodeType === 8 && node.nodeValue === ' /react-text ') {
this._closingComment = node;
break;
}
node = node.nextSibling;
}
}
hostNode = [this._hostNode, this._closingComment];
this._commentNodes = hostNode;
return hostNode;
}, unmountComponent: function () {
this._closingComment = null;
this._commentNodes = null;
ReactDOMComponentTree.uncacheNode(this);
} });

和ReactDOMComponent一样,同样是简单的构造函数和较为复杂的prototype

至此React将一个jsx语法书写的virtual dom 转变成了能够被js解析的React DOMComponent

四、将DOMComponent变成DOM

回到之前的ReactMount.js文件,那里还有最重要的一点,在上面我们知道_renderNewRootComponent是处理virtual dom 对象的最后一环,在这个方法里:

_renderNewRootComponent: function (nextElement, container, shouldReuseMarkup, context) {
//省略环境校验
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
//此步骤为上面提到的将jsx变成DOMComponent
var componentInstance = instantiateReactComponent(nextElement, false);
//在拿到DOMComponent后,进行批量更新处理,其中参数中的container就是在ReactDOM.render中传入的 容器dom元素
ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context); var wrapperID = componentInstance._instance.rootID;
instancesByReactRootID[wrapperID] = componentInstance; return componentInstance;
},

下面就来看看ReactUpdates.batchedUpdates方法做了什么

//其中参数b是插入组件的dom元素,参数a为DOMComponent
function batchedUpdates(callback, a, b, c, d, e) {
//组件注入检测
ensureInjected();
return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

关键方法转移到了batchingStrategy.batchedUpdates方法中,和ReactDOMComponent被绑定到ReactHostComponent.createInternalComponent的方式一样,可以查找到batchingStrategy.batchedUpdates其实源于ReactDefaultBatchingStrategy.js中的batchedUpdates方法:

var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
//如果已经更新过则只执行一次callback
return callback(a, b, c, d, e);
} else {
//否则跳转到transaction.perform 其中 a为DOMComponent b为被注入的DOM
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};

由此我们可以追查到transaction.perform方法中去继续查看:

function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
//原型复合了Transaction模块
_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function () {
return TRANSACTION_WRAPPERS;
}
}); var transaction = new ReactDefaultBatchingStrategyTransaction();

这段代码里用到了callback,该回调函数是在ReactMount.js中传入的:

function batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context) {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(!shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement);
transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);
ReactUpdates.ReactReconcileTransaction.release(transaction);
} //追溯到ReactUpdates.ReactReconcileTransaction
_assign(ReactReconcileTransaction.prototype, Transaction, Mixin);
//并为其附加上面用到的getPooled方法
PooledClass.addPoolingTo(ReactReconcileTransaction);
//addPoolingTo方法如下
var addPoolingTo = function (CopyConstructor, pooler) {
var NewKlass = CopyConstructor;
NewKlass.instancePool = [];
//默认的getPooled方法其实就是DEFAULT_POOLER
NewKlass.getPooled = pooler || DEFAULT_POOLER;
//还附加了poolSize属性 默认是10
if (!NewKlass.poolSize) {
NewKlass.poolSize = DEFAULT_POOL_SIZE;
}
NewKlass.release = standardReleaser;
return NewKlass;
};
var DEFAULT_POOL_SIZE = 10;
var DEFAULT_POOLER = oneArgumentPooler; var oneArgumentPooler = function (copyFieldsFrom) {
//this 其实就是ReactReconcileTransaction
var Klass = this;
//管理instancePool,并通过执行this以生成ReactReconcileTransaction的实例
if (Klass.instancePool.length) {
var instance = Klass.instancePool.pop();
Klass.call(instance, copyFieldsFrom);
return instance;
} else {
return new Klass(copyFieldsFrom);
}
};

callback也同样执行了Transaction的platform方法,只是参数不同

由此可知重头戏是当前目录下的Transaction.js模块,阅读源码前先看此流程图:

/**
* <pre>
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
* </pre>
/**

通过这个示意图可以推测出Transaction方法其实就是黑箱,通过perform将需要执行的方法导入,然后通过wrapper(也就是this)执行初始化方法,然后执行导入的方法,最后统一执行close方法,而wrapper最终保持不变

perform方法如下所示:

perform: function (method, scope, a, b, c, d, e, f) {
var errorThrown;
var ret;
try {
this._isInTransaction = true;
errorThrown = true;
//执行initalizeAll
this.initializeAll(0);
//执行函数
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
//如果执行出错则close
if (errorThrown) {
try {
this.closeAll(0);
} catch (err) {}
} else {
//总之最后都会执行close
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
}

Transaction会在后面详细介绍

通过Transaction的运作实质上是执行了callback函数,其实就是执行batchedMountComponentIntoNode函数,而其中主要又执行了

mountComponentIntoNode函数,源码如下:

function mountComponentIntoNode(wrapperInstance, container, transaction, shouldReuseMarkup, context) {
var markerName;
//省略兼容处理
var markup = ReactReconciler.mountComponent(wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0 /* parentDebugID */
);
//省略兼容处理
wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
//调用_mountImageIntoNode实现元素的插入
ReactMount._mountImageIntoNode(markup, container, wrapperInstance, shouldReuseMarkup, transaction);
}

其中返回的markup对象,经过在ReactDOMComponent中的Mixin.mountComponent方法,将DOMComponent转换为包含dom属性的对象。

Mixin.mountComponent 在生成DOMComponent时作为其构造函数的原型得来的方法

mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
//this 为DOMComponent对象
//设置其属性值
this._rootNodeID = globalIdCounter++;
this._domID = hostContainerInfo._idCounter++;
this._hostParent = hostParent;
this._hostContainerInfo = hostContainerInfo;
//提取props
var props = this._currentElement.props;
//根据标签种类设置其_wrapperState
switch (this._tag) {
case 'audio':
case 'form':
case 'iframe':
case 'img':
case 'link':
case 'object':
case 'source':
case 'video':
this._wrapperState = {
listeners: null
};
//省略transaction操作
......
assertValidProps(this, props);
//根据不同情况设置namespaceURI
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;
//根据 useCreateElement这个标识的取值决定生成什么样的markup对象
if (transaction.useCreateElement) {
var ownerDocument = hostContainerInfo._ownerDocument;
var el;
if (namespaceURI === DOMNamespaces.html) {
if (this._tag === 'script') {
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 {
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;
}
//mountImage就是最后得到markup对象
return mountImage;
}

然后回到ReactMount中的mountComponentIntoNode函数,最后通过_mountImageIntoNode函数将markup插入到目标DOM元素中去

_mountImageIntoNode: function (markup, container, instance, shouldReuseMarkup, transaction) {

    if (shouldReuseMarkup) {
var rootElement = getReactRootElementInContainer(container);
if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
ReactDOMComponentTree.precacheNode(instance, rootElement);
return;
} else {
var checksum = rootElement.getAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);
rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME); var rootMarkup = rootElement.outerHTML;
rootElement.setAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME, checksum); var normalizedMarkup = markup; var diffIndex = firstDifferenceIndex(normalizedMarkup, rootMarkup);
var difference = ' (client) ' + normalizedMarkup.substring(diffIndex - 20, diffIndex + 20) + '\n (server) ' + rootMarkup.substring(diffIndex - 20, diffIndex + 20); if (transaction.useCreateElement) {
while (container.lastChild) {
container.removeChild(container.lastChild);
}
DOMLazyTree.insertTreeBefore(container, markup, null);
} else {
setInnerHTML(container, markup);
ReactDOMComponentTree.precacheNode(instance, container.firstChild);
} }
};

这个函数里面进行diff运算以及插入操作,将markup对象变为真正的dom元素

文章到此结束

React v16-alpha 从virtual dom 到 dom 源码简读的更多相关文章

  1. jQuery("dom").get()的源码分析

    该方法是绑定在jQuery.prototype上的一个静态方法,目的是取出jQuery对象中的某个或全部DOM元素. 使用方法: $("someDOM").get(index); ...

  2. 从template到DOM(Vue.js源码角度看内部运行机制)

    写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(https://github.com/answershuto/learnVue)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些 ...

  3. React key究竟有什么作用?深入源码不背概念,五个问题刷新你对于key的认知

    壹 ❀ 引 我在[react]什么是fiber?fiber解决了什么问题?从源码角度深入了解fiber运行机制与diff执行一文中介绍了react对于fiber处理的协调与提交两个阶段,而在介绍协调时 ...

  4. Java XML DOM解析范例源码

    下边内容内容是关于Java XML DOM解析范例的内容.import java.io.InputStream; import java.util.ArrayList; import java.uti ...

  5. arcgis api 4.x for js 结合 react 入门开发系列初探篇(附源码下载)

    你还在使用 JQuery 或者 Dojo 框架开发 arcgis api 4.x for js 吗?想试试模块化开发吗?随着前端技术的发展,arcgis api 4.x for js 也有了结合 re ...

  6. arcgis api 4.x for js 结合 react 入门开发系列"esri-loader"篇(附源码下载)

    基于上篇的介绍,虽然有比较esri-loader.@arcgis/webpack-plugin,还是觉得有必要需要讲述一下“esri-loader”的开发模式,待大家体验后也会有更直观的感受.本篇文章 ...

  7. React 源码解读参考,理解原理。

    Rubix - ReactJS Powered Admin Template 文档:   http://rubix-docs.sketchpixy.com/ ===================== ...

  8. jQuery2.x源码解析(DOM操作篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) jQuery这个类库最为核心重要的功能就是DOM ...

  9. jQuery 源码分析(二十) DOM操作模块 插入元素 详解

    jQuery的DOM操作模块封装了DOM模型的insertBefore().appendChild().removeChild().cloneNode().replaceChild()等原生方法.分为 ...

随机推荐

  1. Nuget的使用命令

    Nuget的命令行操作都是在程序包管理器控制台下进行的:结构如图:

  2. 【HDU 4150】Powerful Incantation

    题 题意 给你s1,s2两个字符串,求s1中有多少个s2 代码 #include<stdio.h> #include<string.h> int t,len1,len2,pos ...

  3. BZOJ-1227 虔诚的墓主人 树状数组+离散化+组合数学

    1227: [SDOI2009]虔诚的墓主人 Time Limit: 5 Sec Memory Limit: 259 MB Submit: 914 Solved: 431 [Submit][Statu ...

  4. 配置Junit测试程序

    第一步:加载所需要的包:右键-->Build Path-->Configure Build Path-->Libraries-->Add Library-->Junit ...

  5. POJ1976A Mini Locomotive(01背包装+连续线段长度)

    A Mini Locomotive Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 2485   Accepted: 1388 ...

  6. POJ3259Wormholes(判断是否存在负回路)

    Wormholes Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 38300   Accepted: 14095 Descr ...

  7. omnetpp inet

    http://blog.csdn.net/midie/article/details/5086983 omnetpp inet 自带了Mingw编译环境,而不再需要Visual C编译环境了.事实上, ...

  8. 修改eclipse/MyEclipse中包的显示结构为树形

    在右上边三角那里进去设置 选第一个是显示完整的包名,第二个显示的是树形结构,我们一般用第一种,如下图:

  9. [Angularjs]单页应用之分页

    写在前面 在项目中,用到单页应用的分页,当时想到使用滚动加载的方案,可是几次尝试都没配置成功,闲着无聊就弄了一个demo. 系列文章 [Angularjs]ng-select和ng-options [ ...

  10. jquery------.resizable()的使用

    index.jsp //加上这两行代码,右下角会有样式效果<link rel="stylesheet" href="//code.jquery.com/ui/1.1 ...