React源码分析(二)-组件的初始渲染
原文http://realtcg.com/2018/03/17/react-source-code-analysis-2-initial-render/
上一篇文章讲到了React 调用ReactDOM.render
首次渲染组件的前几个过程的源码, 包括创建元素、根据元素实例化对应组件, 利用事务来进行批量更新. 我们还穿插介绍了React 事务的实现以及如何利用事务进行批量更新的实现. 这篇文章我们接着分析后面的过程, 包括调用了哪些事务, 组件插入的过程, 组件生命周期方法什么时候被调用等.
正文
在React 源码中, 首次渲染组件有一个重要的过程, mount
, 插入, 即插入到DOM中, 发生在实例化组件之后. 这是一个不断生成(render)不断插入、类似递归的过程. 让我们一步一步来分析.
使用事务执行插入过程
我们来看首先在插入之前的准备, ReactMount.js中, batchedMountComponentIntoNode
被放到了批量策略batchedUpdates
中执行, batchedMountComponentIntoNode 函数正是执行插入过程的第一步
|
|
这个batchingStrategy
就是ReactDefaultBatchingStrategy
, 因此调用了ReactDefaultBatchingStrategy
的batchedUpdates
, 并将batchedMountComponentIntoNode
当作callback.
在ReactDefaultBatchingStrategy.js中启动了ReactDefaultBatchingStrategyTransaction
事务去执行batchedMountComponentIntoNode
, 以便利用策略控制更新, 而在这个函数中又启动了一个调和(Reconcile)事务, 执行mountComponentIntoNode
进行插入.
|
|
相信你注意到了 ReactUpdates.ReactReconcileTransaction.getPooled
, 这个函数的作用就是从对象池里拿到ReactReconcileTransaction 对象重用.
React优化策略——对象池
在ReactMount.js :
|
|
React 在启动另一个事务之前拿到了这个事务, 从哪里拿到的呢? 这里就涉及到了React 优化策略之一——对象池
GC很慢
首先你用JavaScript声明的变量不再使用时, js引擎会在某些时间回收它们, 这个回收时间是耗时的. 资料显示:
Marking latency depends on the number of live objects that have to be marked, with marking of the whole heap potentially taking more than 100 ms for large webpages.
整个堆的标记对于大型网页很可能需要超过100毫秒
尽管V8引擎对垃圾回收有优化, 但为了避免重复创建临时对象造成GC不断启动以及复用对象, React使用了对象池来复用对象, 对GC表明, 我一直在使用它们, 请不要启动回收.
React 实现的对象池其实就是对类进行了包装, 给类添加一个实例队列, 用时取, 不用时再放回, 防止重复实例化:
PooledClass.js :
|
|
可以看到, React对象池就是给类维护一个实例队列, 用到就pop一个, 不用就push回去. 在React源码中, 用完实例后要立即释放, 也就是申请和释放成对出现, 达到优化性能的目的.
插入过程
在ReactMount.js中, mountComponentIntoNode
函数执行了组件实例的mountComponent
, 不同的组件实例有自己的mountComponent方法, 做的也是不同的事情. (源码我就不上了, 太TM…)
ReactCompositeComponent类型的mountComponent方法:
ReactDOMComponent类型:
ReactDOMTextComponent类型:
整个mount过程是递归渲染的(矢量图):
刚开始, React给要渲染的组件从最顶层加了一个ReactCompositeComponent类型的 topLevelWrapper来方便的存储所有更新, 因此初次递归是从 ReactCompositeComponent 的mountComponent
开始的, 这个过程会调用组件的render函数(如果有的话), 根据render出来的elements再调用instantiateReactComponent
实例化不同类型的组件, 再调用组件的 mountComponent
, 因此这是一个不断渲染不断插入、递归的过程.
总结
React 初始渲染主要分为以下几个步骤:
- 构建一个组件的elements tree(subtree)—— 从组件嵌套的最里层(转换JSX后最里层的createElements函数)开始层层调用
createElements
创建这个组件elements tree. 在这个subtree中, 里层创建出来的元素作为包裹层的props.children; - 实例化组件——根据当前元素的类型创建对应类型的组件实例;
- 利用多种事务执行组件实例的
mountComponent
.- 首先执行topLevelWrapper(ReactCompositeComponent)的mountComponent;
- ReactCompositeComponent的
mountComponent
过程中会先调用render(Composite类型 )生成组件的elements tree, 然后顺着props.children, 不断实例化, 不断调用各自组件的mountComponent 形成循环
- 在以上过程中, 依靠事务进行存储更新、回调队列, 在事务结束时批量更新.
以上是 React源码分析(二)-组件的初始渲染 的全部内容, 来源链接: utcz.com/z/383642.html