標籤:
在使用React進行構建應用時,我們總會有一個步驟將組建或者虛擬DOM元素渲染到真實的DOM上,將任務交給瀏覽器,進而進行layout和paint等步驟,這個函數就是React.render()。首先看下該函數的介面定義:
ReactComponent render( ReactElement element, DOMElement container, [function callback] )
接收2-3個參數,並返回ReactComponent類型的對象,當組件被添加到DOM中後,執行回調。在這裡涉及到了兩個React類型--ReactComponent和ReactElement,著重分析。
ReactElement類型解讀
ReactElement類型通過函數React.createElement()建立,介面定義如下:
ReactElement createElement( string/ReactClass type, [object props], [children ...] )
第一個參數可以接受字串(如“p”,“div”等HTML的tag)或ReactClass,第二個參數為傳遞的參數,第三個為子項目,可以為字串和ReactElement。
下面著重分析createElement的具體實現:
ReactElement.createElement = function(type, config, children) {var propName;// Reserved names are extractedvar props = {};var key = null;var ref = null;if (config != null) {ref = config.ref === undefined ? null : config.ref;key = config.key === undefined ? null : ‘‘ + config.key;// Remaining properties are added to a new props objectfor (propName in config) {if (config.hasOwnProperty(propName) &&!RESERVED_PROPS.hasOwnProperty(propName)) {props[propName] = config[propName];}}}// Children can be more than one argument, and those are transferred onto// the newly allocated props object.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];}props.children = childArray;}// Resolve default propsif (type && type.defaultProps) {var defaultProps = type.defaultProps;for (propName in defaultProps) {if (typeof props[propName] === ‘undefined‘) {props[propName] = defaultProps[propName];}}}return new ReactElement(type,key,ref,ReactCurrentOwner.current,ReactContext.current,props);};var ReactElement = function(type, key, ref, owner, context, props) {// Built-in properties that belong on the elementthis.type = type;this.key = key;this.ref = ref;// Record the component responsible for creating this element.this._owner = owner;// TODO: Deprecate withContext, and then the context becomes accessible// through the owner.this._context = context;if ("production" !== process.env.NODE_ENV) {// The validation flag and props are currently mutative. We put them on// an external backing store so that we can freeze the whole object.// This can be replaced with a WeakMap once they are implemented in// commonly used development environments.this._store = {props: props, originalProps: assign({}, props)};// To make comparing ReactElements easier for testing purposes, we make// the validation flag non-enumerable (where possible, which should// include every environment we run tests in), so the test framework// ignores it.try {Object.defineProperty(this._store, ‘validated‘, {configurable: false,enumerable: false,writable: true});} catch (x) {}this._store.validated = false;// We‘re not allowed to set props directly on the object so we early// return and rely on the prototype membrane to forward to the backing// store.if (useMutationMembrane) {Object.freeze(this);return;}}this.props = props;};
在ReactElement.js中實現了該方法,首先儲存傳入的參數,其中ref和key這兩個參數比較特別,ref用於父組件引用子組件的真實DOM,key用於調和演算法,判斷該組件是否update或remove;儲存children到props中,並根據type是否有defaultProps屬性對props進行mixin;最後建立ReactElement執行個體。其中reactElement有個執行個體屬性_owner,用於儲存所屬的組件。
ReactElement的原型對象只有一個簡單的方法用於判斷是否是ReactElement對象,沒有額外的方法。
綜上,我們可以看出ReactElement有4個屬性:type,ref,key,props,並且輕量,沒有狀態,是一個虛擬化的DOM元素。
ReactClass類型解讀
React的核心是ReactElement類型,但是精髓確實ReactComponent,即組件。但是組件的建立卻並不簡單,我們通過React.createClass建立ReactClass類,它是ReactComponent的建構函式,不同於正常的對象建立,組件的建立由React接管,即我們無須對其執行個體化(new MyComponent())。相對於ReactElement的無狀態,ReactComponent是有狀態的,先看介面定義:
ReactClass createClass(object specification)
傳入的spec參數必須包含render方法,用於渲染虛擬DOM,render返回ReactElement類型;另外還有一些getInitialState和生命週期方法,可以根據需要定義。
下面根據createClass的實現來深入分析:
createClass: function(spec) {var Constructor = function(props, context) {// Wire up auto-bindingif (this.__reactAutoBindMap) {bindAutoBindMethods(this);}this.props = props;this.context = context;this.state = null;// ReactClasses doesn‘t have constructors. Instead, they use the// getInitialState and componentWillMount methods for initialization.var initialState = this.getInitialState ? this.getInitialState() : null;this.state = initialState;};Constructor.prototype = new ReactClassComponent();Constructor.prototype.constructor = Constructor;injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));mixSpecIntoComponent(Constructor, spec);// Initialize the defaultProps property after all mixins have been mergedif (Constructor.getDefaultProps) {Constructor.defaultProps = Constructor.getDefaultProps();}// Reduce time spent doing lookups by setting these on the prototype.for (var methodName in ReactClassInterface) {if (!Constructor.prototype[methodName]) {Constructor.prototype[methodName] = null;}}// Legacy hookConstructor.type = Constructor;return Constructor;}// Constructor的原型var ReactClassComponent = function() {};// assign類似於mixinassign(ReactClassComponent.prototype,ReactComponent.prototype,ReactClassMixin);// mixin到Constructor的原型上function mixSpecIntoComponent(Constructor, spec) {if (!spec) {return;}var proto = Constructor.prototype;// By handling mixins before any other properties, we ensure the same// chaining order is applied to methods with DEFINE_MANY policy, whether// mixins are listed before or after these methods in the spec.if (spec.hasOwnProperty(MIXINS_KEY)) {RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);}for (var name in spec) {if (!spec.hasOwnProperty(name)) {continue;}if (name === MIXINS_KEY) {// We have already handled mixins in a special case abovecontinue;}var property = spec[name];validateMethodOverride(proto, name);if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {RESERVED_SPEC_KEYS[name](Constructor, property);} else {// Setup methods on prototype:// The following member methods should not be automatically bound:// 1. Expected ReactClass methods (in the "interface").// 2. Overridden methods (that were mixed in).var isReactClassMethod =ReactClassInterface.hasOwnProperty(name);var isAlreadyDefined = proto.hasOwnProperty(name);var markedDontBind = property && property.__reactDontBind;var isFunction = typeof property === ‘function‘;var shouldAutoBind =isFunction &&!isReactClassMethod &&!isAlreadyDefined &&!markedDontBind;if (shouldAutoBind) {if (!proto.__reactAutoBindMap) {proto.__reactAutoBindMap = {};}proto.__reactAutoBindMap[name] = property;proto[name] = property;} else {if (isAlreadyDefined) {var specPolicy = ReactClassInterface[name];// For methods which are defined more than once, call the existing// methods before calling the new property, merging if appropriate.if (specPolicy === SpecPolicy.DEFINE_MANY_MERGED) {proto[name] = createMergedResultFunction(proto[name], property);} else if (specPolicy === SpecPolicy.DEFINE_MANY) {proto[name] = createChainedFunction(proto[name], property);}} else {proto[name] = property;if ("production" !== process.env.NODE_ENV) {// Add verbose displayName to the function, which helps when looking// at profiling tools.if (typeof property === ‘function‘ && spec.displayName) {proto[name].displayName = spec.displayName + ‘_‘ + name;}}}}}}}
createClass返回一個Constructor建構函式,它的原型是newReactClassComponent()對象,該對象有mixin的組件的方法(在spec對象中的mixins屬性的對象的方法)和ReactComponent的方法(setState和forceUpdate),並且在mixSpecIntoComponent(Constructor, spec)方法中將spec中實現的方法綁定到Constructor的原型上,在這裡對於非React提供的方法(即個人實現的一些功能函數或者事件處理函數)儲存在原型的__reactAutoBindMap的屬性上。最後再設定Constructor的defaultProps和type(Constructor.type = Constructor)。
在上節中提到了createElement的第一個參數可以是ReactClass,因此在Constructor實現上賦予了type和defaultProps屬性。
React的入口—React.render()
React.render的實現是在ReactMount中,我們通過源碼進行進一步的分析。
render: function(nextElement, container, callback) {var prevComponent = instancesByReactRootID[getReactRootID(container)];if (prevComponent) {var prevElement = prevComponent._currentElement;if (shouldUpdateReactComponent(prevElement, nextElement)) {return ReactMount._updateRootComponent(prevComponent,nextElement,container,callback).getPublicInstance();} else {ReactMount.unmountComponentAtNode(container);}}var reactRootElement = getReactRootElementInContainer(container);var containerHasReactMarkup =reactRootElement && ReactMount.isRenderedByReact(reactRootElement);var shouldReuseMarkup = containerHasReactMarkup && !prevComponent;var component = ReactMount._renderNewRootComponent(nextElement,container,shouldReuseMarkup).getPublicInstance();if (callback) {callback.call(component);}return component;}
如果是第一次掛載該ReactElement,直接添加即可;如果之前已掛載過,則通過instancesByReactRootID擷取渲染之前container的舊組件,即prevComponent,具體通過擷取container的firstChild,並根據緩衝擷取該對象對應的id,並根據id得到prevComponent。每個component對象都有對應的虛擬DOM,即ReactElement,通過shouldUpdateReactComponent(prevElement, nextElement)進行判斷對組件進行update還是delete。
具體shouldUpdateReactComponent的比較演算法是:如果prevElement類型為string或者number,那麼nextElement類型為string或number時為true;如果prevElement和nextElement為object,並且key和type屬性相同,則prevElement._owner == nextElement._owner相等時為true,否則為false。
如果需要更新,則調用ReactMount.._updateRootComponent函數進行Reconciliation,並返回該組件;否則刪除該組件,具體操作則是刪除container的所有子項目。然後判斷shouldReuseMarkup,對於初次掛載的ReactElement而言,該標記為false。最後通過調用_renderNewRootComponent方法將ReactElement渲染到DOM上,並擷取對應的ReactComponent對象,最後執行回調並返回組件對象。
對於_renderNewRootComponent方法,通過調用instantiateReactComponent(nextElement, null)來執行個體化組件,並在ReactMount的緩衝中註冊組件,批量執行更新ReactUpdates.batchedUpdates,最終通過_mountImageIntoNode方法將虛擬節點插入到DOM中。
至此,React中比較重要的方法講解完畢。下一步計劃是分析組件的執行個體化過程,敬請期待。
React入口詳解