1、物料準備html
1.克隆react源碼, github 地址:https://github.com/facebook/react.gitnode
2.安裝gulp react
3.在react源碼根目錄下:git
$npm installes6
$gulp defaultgithub
(建議使用node 6.0+)npm
gulp將文件處理在根目錄下的build文件夾中,打開build查看react的源碼,結構清晰,引用路徑明瞭gulp
2、從生成 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中進行處理,至於其背後有何機制,之後再作討論
3、將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
4、將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元素
文章到此結束