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元素
文章到此结束