前言

在使用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 extracted
var 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 hook
Constructor.type = Constructor; return Constructor;
}
// Constructor的原型
var ReactClassComponent = function() {};
// assign类似于mixin
assign(
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 above
continue;
} 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构造函数,它的原型是new ReactClassComponent()对象,该对象有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中比较重要的方法讲解完毕。下一步计划是分析组件的实例化过程,敬请期待。

ReactJS分析之入口函数render的更多相关文章

  1. 1.2. chromium源代码分析 - chromiumframe - 入口函数

    ChromiumFrame的入口函数在main.cpp中,打开main.cpp.中包含3个类和_tWinMain函数._tWinMain就是我们要找的入口函数.我做了部分注释: int APIENTR ...

  2. twemproxy源码分析1——入口函数及启动过程

    最近工作中需要写一个一致性哈希的代理,在网上找到了twemproxy,结合网上资料先学习一下源码. 一.Twemproxy简介 Twemproxy是memcache与redis的代理,由twitter ...

  3. u-boot-1.1.6第2阶段入口函数start_armboot分析

    学习目标: 1.分析u-boot-1.1.6第2阶段入口函数void start_armboot (void),熟悉该函数所实现的功能 2.为后面能够掌握u-boot-1.1.6如何启动内核过程打下基 ...

  4. module_init宏解析 linux驱动的入口函数module_init的加载和释放

    linux驱动的入口函数module_init的加载和释放 http://blog.csdn.net/zhandoushi1982/article/details/4927579 void free_ ...

  5. jQuery分析(2) - $工厂函数分析

    前言 从这节进入jQuery的世界,首先从jQuery的入口函数开始了解jQuery()或$是如何运作的,这里我给出了一个最小的例子来分析. 回忆 在进入分析代码前我们回想下jQuery的使用方法有哪 ...

  6. 第1阶段——uboot分析之启动函数bootm命令 (9)

    本节主要学习: 详细分析UBOOT中"bootcmd=nand read.jffs2 0x30007FC0 kernel;bootm 0x30007FC0"中怎么实现bootm命令 ...

  7. C++的入口函数

    我们最开始学习c++时,就知道要写一个main()函数,并且知道这是整个函数的入口,但是c++不只有main()函数这一个入口. 一.对于不同的程序函数入口是不同的. main()是WINDOWS的控 ...

  8. 程序入口函数和glibc及C++全局构造和析构

    分类: CRT Machnasim 2011-06-15 17:45 144人阅读 评论(0) 收藏 举报 c++汇编linuxlist语言编译器 1,程序入口函数和初始化 操作系统在装载可执行文件后 ...

  9. C++学习--入口函数

    在学习第一个C++程序的时候发现控制台程序的入口函数是int _tmain而不是main,查了资料才发现_tmain()是为了支持unicode所使用的main一个别名,宏定义在<stdafx. ...

随机推荐

  1. Java ArrayList和Vector、LinkedList与ArrayList、数组(Array)和列表集合(ArrayList)的区别

    ArrayList和Vector的区别ArrayList与Vector主要从二方面来说.  一.同步性:   Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步 ...

  2. 浏览器地址栏输入一个URL后回车,将会发生的事情

    浏览器向DNS服务器查找输入URL对应的IP地址. DNS服务器返回网站的IP地址. 浏览器根据IP地址与目标web服务器在80端口上建立TCP连接 浏览器获取请求页面的html代码. 浏览器在显示窗 ...

  3. STL之priority_queue

    下面以 long long 型队列介绍: Q.empty() // 判断队列是否为空 返回ture表示空 返回false表示空 bool Q.top() // 返回顶端元素的值 元素还在队列里 lon ...

  4. 一些iOS高效开源类库

    因为iOS SDK相对比较底层,所以开发者就得受累多做一些体力活.不过幸运的是,有很多第三方的类库可以用来简化很多不必要的工作.笔者整理了一下在本人学习过程中用到的一些比较有用Objective-C开 ...

  5. 初识Windows程序

    首先,我们创建第一个Windows程序,一共分为4个步骤: 1.打开Visual Studio开发工具 2.选择"文件"→"新建"→"项目" ...

  6. 【转】Web测试方法

    看到好文章,拿过来给大家分享分享! 一.输入框 1.字符型输入框: (1)字符型输入框:英文全角.英文半角.数字.空或者空格.特殊字符“~!@#¥%……&*?[]{}”特别要注意单引号和&am ...

  7. SQL Server 变更数据捕获(CDC)监控表数据

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现过程(Realization) 补充说明(Addon) 参考文献(References) ...

  8. Css 动画的回调

    在做项目中经常会遇到使用动画的情况.以前的情况是用js写动画,利用setTimeout函数或者window.requestAnimationFrame()实现目标元素的动画效果.虽然后者解决了刷新频率 ...

  9. 迷你MVVM框架 avalonjs 实现上的几个难点

    经过两个星期的性能优化,avalon终于实现在一个页面绑定达到上万个的时候不卡顿的目标(angular的限制是2000).现在稍作休息,总结一下avalon遇到的一些难题. 首先是如何监控的问题.所有 ...

  10. Azure PowerShell (7) 使用CSV文件批量设置Virtual Machine Endpoint

    <Windows Azure Platform 系列文章目录> 请注意: - Azure不支持增加Endpoint Range - 最多可以增加Endpoint数量为150 http:// ...