所谓知其然还要知其因此然. 本文将分析 React 15-stable的部分源码, 包括组件初始渲染的过程和组件更新的过程.在这以前, 假设读者已经:javascript
代码架构预览html
首先, 咱们找到React在Github上的地址, 把15-stable版本的源码copy下来, 观察它的总体架构, 这里首先阅读关于源码介绍的官方文档, 再接着看.前端
咱们 要分析的源码在 src 目录下:java
// src 部分目录 ├── ReactVersion.js # React版本号 ├── addons # 插件 ├── isomorphic # 同构代码,做为react-core, 提供顶级API ├── node_modules ├── package.json ├── renderers # 渲染器, 包括DOM,Native,art,test等 ├── shared # 子目录之间须要共享的代码,提到父级目录shared ├── test # 测试代码
分析方法node
一、首先看一些网上分析的文章, 对重点部分的源码有个印象, 知道一些关键词意思, 避免在无关的代码上迷惑、耗费时间;react
二、准备一个demo, 无任何功能代码, 只安装react,react-dom, Babel转义包, 避免分析无关代码;json
三、打debugger; 利用Chrome devtool一步一步走, 打断点, 看调用栈,看函数返回值, 看做用域变量值;浏览器
四、利用编辑器查找代码、阅读代码等安全
咱们知道, 对于通常的React 应用, 浏览器会首先执行代码 ReactDOM.render
来渲染顶层组件, 在这个过程当中递归渲染嵌套的子组件, 最终全部组件被插入到DOM中. 咱们来看看架构
若是看不清这有矢量图
首先, 对于你写的jsx, Babel会把这种语法糖转义成这样:
// jsx ReactDOM.render( <C />, document.getElementById('app') ) // 转义后 ReactDOM.render( React.createElement(C, null), document.getElementById('app') );
没错, 就是调用React.createElement
来建立元素. 元素是什么? 元素只是一个对象描述了DOM树, 它像这样:
{ $$typeof: Symbol(react.element) key: null props: {} // props有child属性, 描述子组件, 一样是元素 ref: null type: class C // type能够是类(自定义组件)、函数(wrapper)、string(DOM节点) _owner: null _store: {validated: false} _self: null _source: null __proto__: Object }
React.createElement
源码在ReactElement.js
中, 逻辑比较简单, 不作分析.
建立出来的元素被看成参数和指定的 DOM container 一块儿传进ReactDOM.render
. 接下来会调用一些内部方法, 接着调用了 instantiateReactComponent
, 这个函数根据element的类型实例化对应的component. 当element的类型为:
ReactDOMTextComponent
;ReactElement时, 说明是react元素, 进一步判断element.type的类型, 当为
ReactDOMComponent
;ReactCompositeComponent
instantiateReactComponent
函数在instantiateReactComponent.js :
/** * Given a ReactNode, create an instance that will actually be mounted. */ function instantiateReactComponent(node(这里node指element), shouldHaveDebugID) { ... // 若是element为空 if (node === null || node === false) { // 建立空component instance = ReactEmptyComponent.create(instantiateReactComponent); } else if (typeof node === 'object') { // 若是是对象 ... // 这里是类型检查 // 若是element.type是字符串 if (typeof element.type === 'string') { //实例化 宿主组件, 也就是DOM节点 instance = ReactHostComponent.createInternalComponent(element); } else if (isInternalComponentType(element.type)) { // 保留给之后版本使用,此处暂时不会涉及到 } else { // 不然就实例化ReactCompositeComponent instance = new ReactCompositeComponentWrapper(element); } // 若是element是string或number } else if (typeof node === 'string' || typeof node === 'number') { // 实例化ReactDOMTextComponent instance = ReactHostComponent.createInstanceForText(node); } else { invariant(false, 'Encountered invalid React node of type %s', typeof node); } ... return instance; }
在调用instantiateReactComponent
拿到组件实例后, React 接着调用了batchingStrategy.batchedUpdates
并将组件实例看成参数执行批量更新.
批量更新是一种优化策略, 避免重复渲染, 在不少框架都存在这种机制. 其实现要点是要弄清楚什么时候存储更新, 什么时候批量更新.
在React中, 批量更新受batchingStrategy
控制,而这个策略除了server端都是ReactDefaultBatchingStrategy
:
不信你看, 在ReactUpdates.js中 :
var ReactUpdatesInjection = { ... // 注入批量策略的函数声明 injectBatchingStrategy: function(_batchingStrategy) { ... batchingStrategy = _batchingStrategy; }, };
在ReactDefaultInjection.js中注入ReactDefaultBatchingStrategy
:
ReactInjection.Updates.injectBatchingStrategy(ReactDefaultBatchingStrategy); // 注入
那么React是如何实现批量更新的? 在ReactDefaultBatchingStrategy.js咱们看到, 它的实现依靠了事务.
在 Transaction.js中, React 介绍了事务:
* <pre> * wrappers (injected at creation time) * + + * | | * +-----------------|--------|--------------+ * | v | | * | +---------------+ | | * | +--| wrapper1 |---|----+ | * | | +---------------+ v | | * | | +-------------+ | | * | | +----| wrapper2 |--------+ | * | | | +-------------+ | | | * | | | | | | * | v v v v | wrapper * | +---+ +---+ +---------+ +---+ +---+ | invariants * perform(anyMethod) | | | | | | | | | | | | maintained * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|--------> * | | | | | | | | | | | | * | | | | | | | | | | | | * | | | | | | | | | | | | * | +---+ +---+ +---------+ +---+ +---+ | * | initialize close | * +-----------------------------------------+ * </pre>
React 把要调用的函数封装一层wrapper, 这个wrapper通常是一个对象, 里面有initialize方法, 在调用函数前调用;有close方法, 在函数执行后调用. 这样封装的目的是为了, 在要调用的函数执行先后某些不变性约束条件(invariant)仍然成立.
这里的不变性约束条件(invariant), 我把它理解为 “真命题”, 所以前面那句话意思就是, 函数调用先后某些规则仍然成立. 好比, 在调和(reconciliation)先后保留UI组件一些状态.
React 中, 事务就像一个黑盒, 函数在这个黑盒里被执行, 执行先后某些规则仍然成立, 即便函数报错. 事务提供了函数执行的一个安全环境.
继续看Transaction.js对事务的抽象实现:
// 事务的抽象实现, 做为基类 var TransactionImpl = { // 初始化/重置实例属性, 给实例添加/重置几个属性, 实例化事务时会调用 reinitializeTransaction: function () { this.transactionWrappers = this.getTransactionWrappers(); if (this.wrapperInitData) { this.wrapperInitData.length = 0; } else { this.wrapperInitData = []; } this._isInTransaction = false; }, _isInTransaction: false, // 这个函数会交给具体的事务实例化时定义, 初始设为null getTransactionWrappers: null, // 判断是否已经在这个事务中, 保证当前的Transaction正在perform的同时不会再次被perform isInTransaction: function () { return !!this._isInTransaction; }, // 顶级API, 事务的主要实现, 用来在安全的窗口下执行函数 perform: function (method, scope, a, b, c, d, e, f) { var ret; var errorThrown; try { this._isInTransaction = true; errorThrown = true; this.initializeAll(0); // 调用全部wrapper的initialize方法 ret = method.call(scope, a, b, c, d, e, f); // 调用要执行的函数 errorThrown = false; } finally { // 调用全部wrapper的close方法, 利用errorThrown标志位保证只捕获函数执行时的错误, 对initialize // 和close抛出的错误不作处理 try { if (errorThrown) { try { this.closeAll(0); } catch (err) {} } else { this.closeAll(0); } } finally { this._isInTransaction = false; } } return ret; }, // 调用全部wrapper的initialize方法的函数定义 initializeAll: function (startIndex) { var transactionWrappers = this.transactionWrappers; // 获得wrapper // 遍历依次调用 for (var i = startIndex; i < transactionWrappers.length; i++) { var wrapper = transactionWrappers[i]; try { ... this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this):null; } finally { if (this.wrapperInitData[i] === OBSERVED_ERROR) { try { this.initializeAll(i + 1); } catch (err) {} } } } }, // 调用全部wrapper的close方法的函数定义 closeAll: function (startIndex) { ... var transactionWrappers = this.transactionWrappers; // 拿到wrapper // 遍历依次调用 for (var i = startIndex; i < transactionWrappers.length; i++) { var wrapper = transactionWrappers[i]; var initData = this.wrapperInitData[i]; var errorThrown; try { ... if (initData !== OBSERVED_ERROR && wrapper.close) { wrapper.close.call(this, initData); } errorThrown = false; } finally { if (errorThrown) { ... try { this.closeAll(i + 1); } catch (e) {} } } } this.wrapperInitData.length = 0; } };
好的, 相信你已经对事务如何实现有了大体了解, 但这只是React事务的抽象实现, 还须要实例化事务并对其增强的配合, 才能发挥事务的真正做用.
刚讲到, 在React中, 批量更新受batchingStrategy
控制,而这个策略除了server端都是ReactDefaultBatchingStrategy
, 而在ReactDefaultBatchingStrategy.js中, 批量更新的实现依靠了事务:
ReactDefaultBatchingStrategy.js :
... var Transaction = require('Transaction');// 引入事务 ... var RESET_BATCHED_UPDATES = { // 重置的 wrapper initialize: emptyFunction, close: function() { ReactDefaultBatchingStrategy.isBatchingUpdates = false; }, }; var FLUSH_BATCHED_UPDATES = { // 批处理的 wrapper initialize: emptyFunction, close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates), }; // 组合成 ReactDefaultBatchingStrategyTransaction 事务的wrapper var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]; // 调用 reinitializeTransaction 初始化 function ReactDefaultBatchingStrategyTransaction() { this.reinitializeTransaction(); } // 参数中依赖了事务 Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, { getTransactionWrappers: function() { return TRANSACTION_WRAPPERS; }, }); var transaction = new ReactDefaultBatchingStrategyTransaction(); // 实例化这类事务 // 批处理策略 var ReactDefaultBatchingStrategy = { isBatchingUpdates: false, // 批量更新策略调用的就是这个方法 batchedUpdates: function(callback, a, b, c, d, e) { var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; // 一旦调用批处理, 重置isBatchingUpdates标志位 ReactDefaultBatchingStrategy.isBatchingUpdates = true; // 避免重复分配事务 if (alreadyBatchingUpdates) { return callback(a, b, c, d, e); } else { return transaction.perform(callback, null, a, b, c, d, e); // 将callback放进事务里执行 } }, };
那么, 为何批量更新的实现依靠了事务呢? 还记得实现批量更新的两个要点吗?
对于这两个问题, React 在执行事务时调用wrappers的initialize方法, 创建更新队列, 而后执行函数 :
口说无凭, 得有证据. 咱们拿ReactDOM.render
会调用的事务ReactReconcileTransaction
来看看是否是这样:
ReactReconcileTransaction.js 里有个wrapper, 它是这样定义的(英文是官方注释) :
var ON_DOM_READY_QUEUEING = { /** * Initializes the internal `onDOMReady` queue. */ initialize: function() { this.reactMountReady.reset(); }, /** * After DOM is flushed, invoke all registered `onDOMReady` callbacks. */ close: function() { this.reactMountReady.notifyAll(); }, };
咱们再看ReactReconcileTransaction
事务会执行的函数mountComponent
, 它在
ReactCompositeComponent.js :
/* * Initializes the component, renders markup, and registers event listeners. */ mountComponent: function( transaction, hostParent, hostContainerInfo, context, ) { ... if (inst.componentDidMount) { if (__DEV__) { transaction.getReactMountReady().enqueue(() => { // 将要调用的callback存起来 measureLifeCyclePerf( () => inst.componentDidMount(), this._debugID, 'componentDidMount', ); }); } else { transaction.getReactMountReady().enqueue(inst.componentDidMount, inst); } } ... }
而上述wrapper定义的close方法调用的this.reactMountReady.notifyAll()
在这
CallbackQueue.js :
/** * Invokes all enqueued callbacks and clears the queue. This is invoked after * the DOM representation of a component has been created or updated. */ notifyAll() { ... // 遍历调用存储的callback for (var i = 0; i < callbacks.length; i++) { callbacks[i].call(contexts[i], arg); } callbacks.length = 0; contexts.length = 0; } }
好累(笑哭), 先写到这吧. 我原本还想一篇文章就把组件初始渲染的过程和组件更新的过程讲完, 如今看来要分开讲了… React 细节太多了, 蕴含的信息量也很大…说博大精深一点不夸张...向React的做者们以及社区的人们致敬!
我以为读源码是一件很费力可是很是值得的事情. 刚开始读的时候一点头绪也没有, 不知道它是什么样的过程, 不知道为何要这么写, 有时候还会由于断点没打好绕了不少弯路…也是硬着头皮一遍一遍看, 结合网上的文章, 就这样云里雾里的慢慢摸索, 不断更正本身的认知.后来看多了, 就常常会有大彻大悟的感受, 零碎的认知开始连通起来, 逐渐摸清了前因后果.
如今以为确实很值得, 本身学到了很多. 看源码的过程就感受是跟做者们交流讨论同样, 思想在碰撞! 强烈推荐前端的同窗们阅读React源码, 大神们智慧的结晶!
未完待续...