1 组件的创建

学习了半年前端了,感觉前端的水确实也很深。做安卓的时候就对React-Native比较感兴趣,开发H5时也使用了一段时间的ReactJS。所以决定好好分析下它的源码。文章中有不对的地方,请大神不吝赐教。

React受大家欢迎的一个重要原因就是可以自定义组件。这样的一方面可以复用开发好的组件,实现一处开发,处处调用,另外也能使用别人开发好的组件,提高封装性。另一方面使得代码结构很清晰,组件间耦合减少,方便维护。ES5创建组件时,调用React.createClass()即可. ES6中使用class myComponent extends React.Component, 其实内部还是调用createClass创建组件。

组件创建我们可以简单类比为Java中ClassLoader加载class。下面来分析下createClass的源码,我们省去了开发阶段错误提示的相关代码,如propType的检查。(if ("development" !== 'production') {}代码段都不进行分析了,这些只在开发调试阶段调用)

createClass: function (spec) {var Constructor = function (props, context, updater) {// 触发自动绑定if (this.__reactAutoBindPairs.length) {bindAutoBindMethods(this);}// 初始化参数this.props = props;this.context = context;this.refs = emptyObject;  // 本组件对象的引用,可以利用它来调用组件的方法this.updater = updater || ReactNoopUpdateQueue;// 调用getInitialState()来初始化state变量this.state = null;var initialState = this.getInitialState ? this.getInitialState() : null;this.state = initialState;};// 继承父类Constructor.prototype = new ReactClassComponent();Constructor.prototype.constructor = Constructor;Constructor.prototype.__reactAutoBindPairs = [];injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));mixSpecIntoComponent(Constructor, spec);// 调用getDefaultProps,并挂载到组件类上。defaultProps是类变量,使用ES6写法时更清晰if (Constructor.getDefaultProps) {Constructor.defaultProps = Constructor.getDefaultProps();}// React中暴露给应用调用的方法,如render componentWillMount。// 如果应用未设置,则将他们设为nullfor (var methodName in ReactClassInterface) {if (!Constructor.prototype[methodName]) {Constructor.prototype[methodName] = null;}}return Constructor;},

createClass主要做的事情有

  1. 定义构造方法Constructor,构造方法中进行props,refs等的初始化,并调用getInitialState来初始化state
  2. 调用getDefaultProps,并放在defaultProps类变量上。这个变量不属于某个单独的对象。可理解为static 变量
  3. 将React中暴露给应用,但应用中没有设置的方法,设置为null。

2 对象的创建

JSX中创建React元素最终会被babel转译为createElement(type, config, children), babel根据JSX中标签的首字母来判断是原生DOM组件,还是自定义React组件。如果首字母大写,则为React组件。这也是为什么ES6中React组件类名必须大写的原因。如下面代码

<div className="title" ref="example"><span>123</span>    // 原生DOM组件,首字母小写<ErrorPage title='123456' desc={[]}/>    // 自定义组件,首字母大写
</div>

转译完后是

React.createElement('div',    // type,标签名,原生DOM对象为String{className: 'title',ref: 'example'},   // config,属性React.createElement('span', null, '123'),   // children,子元素React.createElement(// type,标签名,React自定义组件的type不为String.// _errorPage2.default为从其他文件中引入的React组件_errorPage2.default,    {title: '123456',desc: []})   // children,子元素
)

下面来分析下createElement的源码

ReactElement.createElement = function (type, config, children) {var propName;// 初始化参数var props = {};var key = null;var ref = null;var self = null;var source = null;// 从config中提取出内容,如ref key propsif (config != null) {ref = config.ref === undefined ? null : config.ref;key = config.key === undefined ? null : '' + config.key;self = config.__self === undefined ? null : config.__self;source = config.__source === undefined ? null : config.__source;// 提取出config中的prop,放入props变量中for (propName in config) {if (config.hasOwnProperty(propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {props[propName] = config[propName];}}}// 处理children,挂到props的children属性下// 入参的前两个为type和config,后面的就都是children参数了。故需要减2var childrenLength = arguments.length - 2;if (childrenLength === 1) {// 只有一个参数时,直接挂到children属性下,不是array的方式props.children = children;} else if (childrenLength > 1) {// 不止一个时,放到array中,然后将array挂到children属性下var childArray = Array(childrenLength);for (var i = 0; i < childrenLength; i++) {childArray[i] = arguments[i + 2];}props.children = childArray;}// 取出组件类中的静态变量defaultProps,并给未在JSX中设置值的属性设置默认值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源码

var ReactElement = function (type, key, ref, self, source, owner, props) {var element = {// This tag allow us to uniquely identify this as a React Element$$typeof: REACT_ELEMENT_TYPE,// ReactElement对象上的四个变量,特别关键type: type,key: key,ref: ref,props: props,// Record the component responsible for creating this element._owner: owner};return element;
}

可以看到仅仅是给ReactElement对象内的成员变量赋值而已,不在赘述。

流程如如下

3 组件对象初始化

在mountComponent()挂载组件中,会进行组件渲染,调用到instantiateReactComponent()方法。这个过程我们在React生命周期方法中再详细讲述,这里有个大体了解即可。instantiateReactComponent()根据ReactElement中不同的type字段,创建不同类型的组件对象。源码如下

// 初始化组件对象,node是一个ReactElement对象,是节点元素在React中的表示
function instantiateReactComponent(node) {var instance;var isEmpty = node === null || node === false;if (isEmpty) {// 空对象instance = ReactEmptyComponent.create(instantiateReactComponent);} else if (typeof node === 'object') {// 组件对象,包括DOM原生的和React自定义组件var element = node;// 根据ReactElement中的type字段区分if (typeof element.type === 'string') {// type为string则表示DOM原生对象,比如div span等。可以参看上面babel转译的那段代码instance = ReactNativeComponent.createInternalComponent(element);} else if (isInternalComponentType(element.type)) {// 保留给以后版本使用,此处暂时不会涉及到instance = new element.type(element);} else {// React自定义组件instance = new ReactCompositeComponentWrapper(element);}} else if (typeof node === 'string' || typeof node === 'number') {// 元素是一个string时,对应的比如<span>123</span> 中的123// 本质上它不是一个ReactElement,但为了统一,也按照同样流程处理,称为ReactDOMTextComponentinstance = ReactNativeComponent.createInstanceForText(node);} else {// 无处理}// 初始化参数,这两个参数是DOM diff时用到的instance._mountIndex = 0;instance._mountImage = null;return instance;
}

故我们可以看到有四种创建组件元素的方式,同时对应四种ReactElement

  1. ReactEmptyComponent.create(), 创建空对象ReactDOMEmptyComponent
  2. ReactNativeComponent.createInternalComponent(), 创建DOM原生对象 ReactDOMComponent
  3. new ReactCompositeComponentWrapper(), 创建React自定义对象ReactCompositeComponent
  4. ReactNativeComponent.createInstanceForText(), 创建文本对象 ReactDOMTextComponent

下面分别分析这几种对象,和创建他们的过程。

ReactDOMEmptyComponent

由ReactEmptyComponent.create()创建,最终生成ReactDOMEmptyComponent对象,源码如下

var emptyComponentFactory;var ReactEmptyComponentInjection = {injectEmptyComponentFactory: function (factory) {emptyComponentFactory = factory;}
};var ReactEmptyComponent = {create: function (instantiate) {return emptyComponentFactory(instantiate);}
};ReactEmptyComponent.injection = ReactEmptyComponentInjection;ReactInjection.EmptyComponent.injectEmptyComponentFactory(function (instantiate) {// 前面比较绕,关键就是这句话,创建ReactDOMEmptyComponent对象return new ReactDOMEmptyComponent(instantiate);
});// 各种null,就不分析了
var ReactDOMEmptyComponent = function (instantiate) {this._currentElement = null;this._nativeNode = null;this._nativeParent = null;this._nativeContainerInfo = null;this._domID = null;
};

ReactDOMComponent

由ReactNativeComponent.createInternalComponent()创建。这里注意原生组件不代表是DOM组件,而是React封装过的Virtual DOM对象。React并不直接操作原生DOM。

大家可以自己看ReactDOMComponent的源码。重点看下ReactDOMComponent.Mixin

ReactDOMComponent.Mixin = {mountComponent: function (transaction, nativeParent, nativeContainerInfo, context) {},_createOpenTagMarkupAndPutListeners: function (transaction, props){},_createContentMarkup: function (transaction, props, context) {},_createInitialChildren: function (transaction, props, context, lazyTree) {}receiveComponent: function (nextElement, transaction, context) {},updateComponent: function (transaction, prevElement, nextElement, context) {},_updateDOMProperties: function (lastProps, nextProps, transaction) {},_updateDOMChildren: function (lastProps, nextProps, transaction, context) {},getNativeNode: function () {},unmountComponent: function (safely) {},getPublicInstance: function () {}
}

其中暴露给外部的比较关键的是mountComponent,receiveComponen, updateComponent,unmountComponent。他们会引发React生命周期方法的调用,下一节再讲。

ReactCompositeComponent

由new ReactCompositeComponentWrapper()创建,重点看下ReactCompositeComponentMixin

var ReactCompositeComponentMixin = {// new对应的方法,创建ReactCompositeComponent对象construct: function(element) {},mountComponent,   // 初始挂载组件时被调用,仅一次performInitialMountWithErrorHandling, // 和performInitialMount相近,只是多了错误处理performInitialMount,  // 执行mountComponent的渲染阶段,会调用到instantiateReactComponent,从而进入初始化React组件的入口getNativeNode,unmountComponent, // 卸载组件,内存释放等工作receiveComponent,performUpdateIfNecessary,updateComponent,  // setState后被调用,重新渲染组件attachRef,    // 将ref指向组件对象,这样我们就可以利用它调用对象内的方法了detachRef,    // 将组件的引用从全局对象refs中删掉,这样我们就不能利用ref找到组件对象了instantiateReactComponent,  // 初始化React组件的入口,在mountComponent时的渲染阶段会被调用
}

ReactDOMTextComponent

由ReactNativeComponent.createInstanceForText()创建,我们也不细细分析了,主要入口代码如下,大家可以自行分析。

var ReactDOMTextComponent = function (text) {this._currentElement = text;this._stringText = '' + text;
};_assign(ReactDOMTextComponent.prototype, {mountComponent,receiveComponent,getNativeNode,unmountComponent
}

react 调用组件方法_React源码分析1 — 组件和对象的创建(createClass,createElement)...相关推荐

  1. React Native 源码分析(三)——Native View创建流程

    1.React Native 源码分析(一)-- 启动流程 2.React Native 源码分析(二)-- 通信机制 3.React Native 源码分析(三)-- Native View创建流程 ...

  2. View的invalidate()方法的源码分析

    首先要明白invalidate()方法是做什么的? View#invalidate(): /*** Invalidate the whole view. If the view is visible, ...

  3. 《微信小程序-进阶篇》Lin-ui组件库源码分析-列表组件List(一)

    大家好,这是小程序系列的第二十篇文章,在这一个阶段,我们的目标是 由简单入手,逐渐的可以较为深入的了解组件化开发,从本文开始,将记录分享lin-ui的源码分析,期望通过对lin-ui源码的学习能加深组 ...

  4. 创建线程的三种方法_Netty源码分析系列之NioEventLoop的创建与启动

    前言 前三篇文章分别分析了 Netty 服务端 channel 的初始化.注册以及绑定过程的源码,理论上这篇文章应该开始分析新连接接入过程的源码了,但是在看源码的过程中,发现有一个非常重要的组件:Ni ...

  5. springmvc项目在启动完成之后执行一次方法_SpringMVC源码分析

    一 SpringMVC运行原理 二 SpingMVC源码分析 1 DispatcherServlet 1.1 DispatcherServlet继承结构 ServletConfig对象获取Init标签 ...

  6. Java 8中Collectors.groupingBy方法空指针异常源码分析

    现在有这样的一个需求:老板让把所有的员工按年龄进行分组,然后统计各个年龄的人数. 这个需求,如果是在数据库中,可以直接使用一个 group by 语句进行统计即可,那么在 Java 中的话,可以借助于 ...

  7. vue-org-tree 组织结构图组件应用及源码分析

    需求: 最近业务需要做类似思维导图的组织结构树功能,需要能动态增删改节点,且每层的节点样式并不相同 可用插件: 网上能找到的组织结构图插件有: 1.orgchart.js 地址:https://git ...

  8. php中的setinc,thinkphp3.2.0中setInc方法的源码分析

    下面为大家分享一篇thinkphp3.2.0 setInc方法 源码全面解析,具有很好的参考价值,希望对大家有所帮助. 我们先来看一下setInc的官方示例: 需要一个字段和一个自增的值(默认为1) ...

  9. Java代码怎么取消订阅功能,RxJava2 中多种取消订阅 dispose 的方法梳理( 源码分析 )...

    Github 相关代码: Github地址 一直感觉 RxJava2 的取消订阅有点混乱, 这样也能取消, 那样也能取消, 没能系统起来的感觉就像掉进了盘丝洞, 迷乱... 下面说说这几种情况 几种取 ...

最新文章

  1. Linux/Unix shell 监控Oracle告警日志(monitor alter log file)
  2. 算法训练 素因子去重
  3. Spring MVC实现Spring Security,Spring Stomp websocket Jetty嵌入式运行
  4. Python__模拟实现一个ATM+购物商城程序
  5. 【学生选课系统经典】C#与SQLSERVER连接:ASP.NET网站(服务器端,IIS发布)
  6. 仅使用HTML和CSS实现的标签云效果
  7. linux(2):linux命令查看开放哪些端口
  8. 【PAT - 甲级 - 1018】Public Bike Management (带权最短路,多条最短路中加条件,DFS)
  9. c#c#继承窗体_C#继承能力问题和解答 套装5
  10. MySql 应该选择普通索引 还是唯一 索引???
  11. google android ui,UI Automator
  12. 好企业:不是走得多快而是走得多稳
  13. MediaInfo源代码分析 4:Inform()函数
  14. SQL查询-将列转换成字符串(for xml path)
  15. 质量名人简介——朱兰(Joseph H.Juran)(转载)
  16. html日期选择框源码,日期选择控件实例源码(带节假日)
  17. unity 图片改成astc 所有机型机型检测
  18. 爬虫第四关——寻找周杰伦
  19. 联想ghost重装系统_史上最全的重装ghost系统错误解决方法大全
  20. 《德鲁克管理思想精要》读书笔记5 - 人事、创新、创业

热门文章

  1. 他山之石:五个互联网英雄的创业启示!
  2. imitativesimulate
  3. Leetcode62 DP
  4. win10如何修改文件拓展名?
  5. 关于Android中XML解析方式
  6. 基于labview的温湿度数据采集_【零偏原创】基于FPGA的多路SPI接口并行数据采集系统...
  7. 对接多种三方的设计模式_死磕设计模式之适配器模式
  8. 暗备用的运行状态_备用发电机管理制度
  9. stm32怎么加载字库_收藏 | STM32单片机超详细学习汇总资料(二)
  10. ajax被token拦截,vue中封装ajax请求,并且拦截请求在请求头中添加token