React v16-alpha 从virtual dom 到 dom 源码简读

一、物料准备

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元素

文章到此结束

上一篇:Virtual DOM 虚拟DOM的理解(转)


下一篇:我心中的核心组件(可插拔的AOP)~第五回 消息组件