mixSpecIntoComponent详解

在创建复合组件调用React.createClass时,传递的参数被称为spec(应该是specification的缩写),spec中可以定义render, componentWillMount等方法,这些方法都会被添加到创建的类上面。 但,并不能直接将spec中的方法添加到新类上就结束,为什么呢?因为有可能spec中定义的方法,与其他Mixin中定义的方法重名了,这个时候不能简单的进行覆盖,而是要不同的方法不同的处理。 ReactCompositeCompoentInterfac中定义了一些特殊方法/属性在进行mixin的处理策略,处理策略有3种,分别是:DEFINE_ONCE,DEFINE_MANY和DEFINE_BASE。 DEFINE_ONCE:意味着遵守该策略的方法或属性,只能出现一次,不允许多处定义,比如props属性和getInitialState方法,都只允许被定义一次,但被定义的属性或方法既可以出现在Mixin中,也可以出现在spec中。 DEFINE_MANY:策略允许某属性或者方法被重复定义,比如componentWillMount方法,继承自多个mixin,spec中也可能会定义,这些被定义了的componentWillMount方法都会被执行。 OVERRIDE_BASE:spec中出现的方法,将直接覆盖ReactCompositeComponent类中的方法 mixSpecIntoComponent函数负责将spec添加到给定的类上,其具体的执行流程,就是遍历spec中的自有元素,查询该属性名是否存在对应的specPolicy。 若ReactCompositeComponentMixin中定义了同名属性或方法,则其对应的策略应当为OVERRIDE_BASE,…

原生组件

原生组件(ReactNativeComponent)是React框架内部处理HTML元素的组件,它继承自ReactComponent。由于HTML元素并没有内部状态一说,只需要通过维护属性(props)即可。 构造函数 原生组件的初始化函数记录了对应的HTML元素标签信息,以方便在渲染时生成相应的HTML代码。HTML标签一般都是闭合的,当然也有例外,例如<br />,<embed />, <hr />等,因此,构造函数包含两个参数,第一个参数表示该元素的标签名称,第二个参数指示该标签是否可以自闭合。 function ReactNativeComponent(tag, omitClose) { this._tagOpen = '<' + tag + ' '; this._tagClose = omitClose ? '' : '</' + tag + '>…

React中的属性(Props)和状态(State)

很多React教程都会指出,属性(props)是只读的,这样讲其实不对,ReactComponent源码中不就包含了setProps和replaceProps两个接口吗?它们就是拿来更新props的。并且,就如同setState一样,调用了setProps同样也会使得组件进行重新渲染。 除了属性,你一定还听说过状态(State)。确切的说,状态是定义在复合组件(ReactCompositeComponent)中,ReactNativeComponent就没有状态一说。 状态和属性,更新时都会触发组件更新,为什么复合组件中既包含了属性、又包含了状态,但ReactNativeComponent组件却又不需要状态呢? 这里简单介绍一下ReactNativeComponent,可以简单理解ReactNativeComponent就是HTML标签元素对应的React组件。每一个HTML标签,在执行完render之后,都会被转化成一个ReactNativeComponent对象。 再来回到这个问题,属性、状态到底有什么区别呢? 区别在于,属性是外部传递,状态是内部变化。组件的初始化函数construct,接受的第一个传参就是props,props一般都是外部传递进来的值,但在复合组件初始化时,并不会传递状态。父组件可以通过传递不同的props给子组件,使得子组件发生更新。 理清了属性和状态的区别,这时你就能理解,为什么ReactNativeComponent不需要状态。因为ReactNativeComponent就是对应了一个HTML元素,HTML元素只需要通过属性就能表示其数据信息,并不会发生状态变化,而复合组件是用户自定义的,有可能存在复杂的内部状态变迁,因此状态被定义在ReactCompositeComponent中。…

React缓冲池的性能抖动

上上周在进行团队分享时,有人提出了对React缓冲池性能的质疑。 起初我坚持认为,空间换时间,缓冲池对性能肯定是有提升效果的,但通过几组对照实验,发现缓冲池在一定情况下,的确有一定的性能提升,但当数据规模增大时,不但不会提升性能,反而会更加慢。 细想一下,还是能想明白,缓冲池本身的适用情景,频繁需要使用某种对象,但是是循环利用的情况。比如运行阶段一共需要用到10000次某种类的实例,缓冲池大小满足峰值需要,假设峰值为100,同时尽可能的串行请求该对象,避免新的内存申请。这种情况下,由于极大的复用了现有的内存对象,理论上讲肯定是有性能提升的。但实际项目中这样的情况或许并不多,所以不见得能体现出缓冲池的提升效果。 而更有甚者,某些情况下,还可能导致缓冲池性能抖动,我总结了一下,大概有以下几种: 缓冲池poolSize设置过小,而同一时刻需要用到的实例数很多。比如缓冲池大小只有10,而同时需要的实例数量为10000,这时缓冲池形同虚设 js虚拟机在内存管理时做了一定的优化,导致大量创建实例的开销下降,反而优于使用缓冲池的情况。一般来说,对象创建之后随机销毁,这样的测试例子达到一定规模时,时间开销就会小于使用了缓冲池的情况 缓冲池对象的Pooler过程开销较大,甚至超过了new的开销 不能迷信过去的知识,时代在变化,js的执行引擎越来越牛逼了。…

React中的ChainedFunction

之前提到过,React通过Mixin机制实现了类的多重继承,而当类从多个父类继承方法时,就有可能出现同名方法在多个Mixin中被定义的情况,这时应该如何处理呢? React中通过SpecPolicy和ChainedFunction进行了处理。 SpecPolicy ReactCompositeComponent文件中定义了一个枚举对象SpePolicy,它包含三个属性, 分别是 DEFINE_ONCE DEFINE_MANY DEFINE_BASE 与此同时,在React还定义了ReactCompositeComponentInterface,并在接口中定义了一些方法和属性的SpecPolicy。 当基类从Mixin继承某个属性/方法时,会查询它的SpecPolicy进行检测: 如果ReactCompositeComponentMixin中定义了同名属性,那么该属性的SpecPolicy不能为OVERRIDE_BASE 如果该类的原型对象上面存在同名属性,那么该属性的SpecPolicy必须为DEFINE_MANY DEFINE_ONCE的属性不能在多个Mixin中同时出现 ChainedFunction 当一个方法同时在多个Mixin中定义了,并且其SpecPolicy为DEFINE_MANY时,如何保证每个Mixin中的同名函数都被执行了呢? 答案是使用ChainedFunction。多个Mixin中的同名函数,会通过调用createChainedFunction方法,覆盖proto中原有的同名函数,将createChainedFunction的返回值作为新的同名函数存储到proto中。如果之后再出现了同名函数,重复刚才的步骤,将创建的chainedFunction和新的同名函数作为参数,再创建一个ChainedFunction。 createChainedFunction源码如下: function createChainedFunction(one, two) { return…

ReactReconcileTransaction

之前简要介绍过React中的事务机制,https://www.hoyt-tian.com/reactzhong-de-transaction/ 。事务机制渗透在React的更新操作中,这里再更详细的解析一下。 Transaction React源代码中跟事务相关的核心文件有两个,一个是src/core/ReactReconcileTransaction.js,另一个是src/utils/Transaction.js;其中Transaction.js中以Mixin的形式定义了事务接口的4个核心方法,分别是reinitializeTransaction,perform,initializeAll,closeAll,除此之外还有几个辅助函数,其中perform方法包含了整个事务流程,reinitializeTransaction用来重复利用缓冲池中的transaction对象,将之进行重新初始化。perform的代码截取出来如下: perform: function(method, scope, a, b, c, d, e, f) { // _isInTransaction标记了当前事务的状态 throwIf(this.isInTransaction(), DUAL_TRANSACTION); var memberStart = Date.now(…

React的状态更新

当一个ReactCompositeComponent调用setState时,组件就会开始更新,其背后的机制到底是怎样的呢?先从熟悉的setState来,还是先看源码 setState: function(partialState) { // Merge with `_pendingState` if it exists, otherwise with existing state. this.replaceState(merge(this._pendingState || this.state, partialState)); } 可见,setState方法只是做了一步预处理,将传入的partialState和当前的(pending)state进行了合并,然后再调用了replaceState方法。核心的主要内容还是在replaceState中。 replaceState方法里,首先对组件当前的状态进行了断言。ComponentLifeCycle必须为MOUNTED或者CompositeLifeCycle为MOUNTING,且CompositeLifeCycle不能为RECEIVING_STATE或者UNMOUNTING(注:ComponentLifeCycle是在ReactComponent中定义的,而后者是在ReactCompositeComponent中定义的)。 断言通过以后,从事务池中取出一个事务。(关于事务的更多信息,可以查看我之前的文章https://www.hoyt-tian.com/reactde-shi-wu-ji-zhi/ ),然后在事务中完成this.…

React.createClass

从源码可以看到,React.createClass = ReactCompositeComponent.createClass, 因此真正的实现其实是在ReactCompositeComponent中。 简单来说,createClass时就做了两件事情。第一件事情,就是继承了相关基类的方法;第二件事情,就是做了一个wrapper,方便后续的调用。 继承细节 继承相关的代码如下: createClass: function(spec) { var Constructor = function() {}; Constructor.prototype = new ReactCompositeComponentBase(); Constructor.prototype.constructor = Constructor; mixSpecIntoComponent(Constructor, spec); invariant( Constructor.prototype.render, 'createClass(...): Class specification must implement a `render` method.' ); // ... 暂时省略后续代码,先讲前半部分 } 这段代码,首先定一个空函数Constructor,然后更新了它的prototype,…

React的事件机制

React使用了SyntheticEvent来实现跨浏览器的事件支持,与一般的做法不同的是,React并不直接将事件的监听函数挂在在对应的DOM节点上,而是统一挂载在document上,然后再进行处理。整个过程还是比较复杂的,React的源代码中也以注释的形式进行了整个框架的介绍,如下图。 ![alt](/content/images/2017/09/QQ20170926-0-1.png) 整个事件处理,可以分成两部分来讲,一是ReactComponent在创建时如何进行事件绑定;二是,当一个NativeEvent触发以后,如何进行事件传递。后者的流程相对更简单,但需要先把绑定的过程介绍一遍。 # 事件绑定流程 事件绑定的流程还是比较复杂的,先上图,我画了两张图才能把这个流程表示完,~~且图片放大了才能看清 :(~~(点击图片可以查看大图,为了让大图看清楚,特意做了一个插件,https://www.hoyt-tian.com/gei-ghosttian-jia-suo-fang-tu-pian-gong-neng/) ![alt](/content/images/2017/09/ReactEventFlow1.png) ![alt](/content/images/2017/09/ReactEventFlow2.png)…