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中。…

NodeList研究

先来看一段代码 const hchild = document.head.childNodes const bchild = document.body.childNodes const hsize = hchild.length const bsize = bchild.length document.head.appendChild(bchild[0]) console.log(`${hchild.length === hsize} and ${bchild.length === bsize}`) 浏览器中执行后,输出是什么呢? 答,输出是false和false 第一个false还稍微容易理解一些,hchild是指向head.childNodes的指针,调用appendChild后,长度增加了1 。 第二个false一开始让我懵逼了,为什么bchild.length也发生了变化呢?因为body.childNodes返回的并不是数组,而是一个NodeList。 Although NodeList…

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…

React v0.3.0中的keyOf

React中有一个很特别的工具函数——keyOf,它定义在src/vendor/core/keyOf.js文件中,源代码不长,就是返回了对象的(第一个)key(必须是ownProperty的key),如下: var keyOf = function(oneKeyObj) { var key; for (key in oneKeyObj) { if (!oneKeyObj.hasOwnProperty(key)) { continue; } return key; } return null; }; 既然如此,为什么不直接使用字符串常量呢? 使用字符串常量最大的问题,就在于压缩混淆时,无法参与优化。而使用keyOf这个函数的好处,就在于,压缩混淆阶段,其具体值可以被替换成任意字符串,参与优化,同时又不影响源代码中的语义,可谓一举两得。…

React v0.3.0在本地调试的问题

在阅读源代码时,不免想要实际调试一下其具体的执行过程。由于React v0.3.0的编译配置有些老旧,直接按照ReadMe.md中的描述,是无法成功编译的。会导致出错的原因有两个: 一是,grunt命令工具集更新后,原来的文件遍历、枚举调用有了变化,之前的脚本在执行时会出错;二是依赖的库esprima名称有些小变化,如果安装了更新后的版本,需要手动把名字跟着改一下。 由于Grunt工具我并不熟悉,在尝试修复问题没有成功之后,我选择了直接去github上下载编译好的React v0.3.0的文件,节约了不少精力。 把examples目录和build目录配置到nginx中,就可以在本地查看react自己的示例代码了。在浏览器中使用正确的url打开examples/basic/index.html,可能会发现控制台有报错,这是因为启用了严格模式之后,如果event对象的targe属性是只读的,执行 normalized.target = textNodeNormalizedTarget; 就会在控制台报重复抛出异常。为了阻止这种错误提示重复出现,可以修改源代码,把这行代码修改成 Object.defineProperty(normalized, "target", { value : textNodeNormalizedTarget…

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.…