zoukankan      html  css  js  c++  java
  • React入口详解

    在使用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 object
    		for (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 props
    	if (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 element
    	this.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-binding
    			if (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 merged
    		if (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中。

  • 相关阅读:
    [翻译] FreeStreamer 在线流媒体播放
    [转] 每个程序员都必须遵守的编程原则
    iOS7以下设备获取mac地址
    iOS中alloc与init
    ON、WHERE、HAVING的区别
    在SQL语言中,join什么时候用,什么时候不用啊?请高手举例解释一下。谢谢
    你能识别这些科技公司的真假logo吗?
    in 和 exist 区别
    union和union all的区别
    Mysql避免全表扫描sql查询优化 .
  • 原文地址:https://www.cnblogs.com/lds2014/p/4910417.html
Copyright © 2011-2022 走看看