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 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中比较重要的方法讲解完毕。下一步计划是分析组件的实例化过程,敬请期待。

时间: 2024-10-01 20:22:55

React入口详解的相关文章

vue和react全面对比(详解)

vue和react对比(详解) 放两张图镇压小妖怪 本文先讲共同之处, 再分析区别 大纲在此: 共同点: a.都使用虚拟dom b.提供了响应式和组件化的视图组件 c.注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库. 区别: a.优化 b.HTML&CSS c.构建工具 d.数据绑定 e.状态管理 f.路由 g.渲染性能 h.数据更新 i.开发模式及规模 j.使用场景 k.服务器端渲染(SSR) l.扩展(高阶组件和mixin) 1.都使用虚拟DOM vue:Vue在2.0

【React Native开发】React Native控件之Touchable*系列组件详解(18)

转载请标明出处: http://blog.csdn.net/developer_jiangqq/article/details/50630984 本文出自:[江清清的博客] (一)前言 [好消息]个人网站已经上线运行,后面博客以及技术干货等精彩文章会同步更新,请大家关注收藏:http://www.lcode.org 今天我们一起来看一下Touchable*系列组件的使用详解,该系列组件包括四种分别为:TouchableHighlight,TouchableNativeFeedback,Touch

React—组件生命周期详解

React-组件生命周期详解 转自 明明的博客  http://blog.csdn.net/slandove/article/details/50748473 (非原创) 版权声明:转载请注明出处,欢迎加入QQ群(115402375)讨论!博客编写已经转移到http://blog.csdn.net/limm33 在组件的整个生命周期中,随着该组件的props或者state发生改变,它的DOM表现也将有相应的改变,一个组件就是一个状态机,对于特定的输入,它总会返回一致的输出. React为每个组件

zend framework2 之入口文件详解以及原理解析

zend framework2 之入口文件详解 转载请注明出处,尊重原创:http://blog.csdn.net/a437629292/article/details/41545297,谢谢! 一.基本配置解析 1. 详细配置: 如下代码: <?php // 定义程序目录 defined('APPLICATION_PATH') || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application')); // D

React Native之this详解

this引起的错误详解 我们在学习React Native的过程中,肯定经常遇见过undefined is not an object这样的问题吧,尤其是刚开始学习的时候,使用this.props或者this.setState的时候会报类似于如下错误: 接下来我们来分析以下到底是什么原因造成的错误, 根据错误的提示,找到报错的代码,我们会发现: 报错的都是this.props.?或者this.setState(?),都是出现在有this的地方 报错的原因是没有定义该对象,而我们都知道this代表

React Native组件、生命周期及属性传值props详解

创建组件的三种方式 第一种:通过ES6的方式创建 /** * 方式一 :ES6 */ export default class HelloComponent extends Component { render (){ return <Text style={{fontSize:20,backgroundColor:'red'}}>Hello</Text> } } 第二种:通过ES5的方式创建 /** * 方式二:ES5 */ var HelloComponent= React.c

React Native之属性类型检查机制详解 PropType 变成 prop-types

属性确认的作用 使用 React Native 创建的组件是可以复用的,所以我们开发的组件可能会给项目组其他同事使用.但别人可能对这个组件不熟悉,常常会忘记使用某些属性,或者某些属性传递的数据类型有误. 因此我们可以在开发 React Native 自定义组件时,可以通过属性确认来声明这个组件需要哪些属性.这样,如果在调用这个自定义组件时没有提供相应的属性,则会在手机与调试工具中弹出警告信息,告知开发者该组件需要哪些属性. React Native已经升级到0.51.0了,版本升级很快,但是对老

2019年17道高频React面试题及详解

以下面试题来源于github项目前端面试指南,那里有超过200道高频前端面试题及答案,目前拥有1400star. 为什么选择使用框架而不是原生?框架的好处: 组件化: 其中以 React 的组件化最为彻底,甚至可以到函数级别的原子组件,高度的组件化可以是我们的工程易于维护.易于组合拓展.天然分层: JQuery 时代的代码大部分情况下是面条代码,耦合严重,现代框架不管是 MVC.MVP还是MVVM 模式都能帮助我们进行分层,代码解耦更易于读写.生态: 现在主流前端框架都自带生态,不管是数据流管理

React:react-router-dom 详解

使用react构建单页面应用: 实现方法:(1)react-router (2)react-router-dom react-router: 实现了路由的核心功能,而react-router-dom依赖react-router, react-router-dom: 基于react-router,加入了在浏览器运行环境下的一些功能: Link组件,会渲染一个a标签: BrowserRouter组件,使用pushState和popState事件构建路由: HashRouter组件,使用window.