render微观细节
start
这里从Component开始分析,因为我们一般使用 react时候最基础的用法是:
1 | // jsx file |
所以这里暂时以这个为目标,看看react最终是怎样实现这个jsx到js再到html上显示出HelloWorld的
当代码开始处理时候首先时通过 babel的预处理,这段代码关键的地方被转译后是这样
1 | ReactDOM.render( |
不过值得一提是createElement实质上并不会直接处理
如果HelloMessage里面还有更多元素,那么第三个参数依旧用React.createElement来遍历子节点创建元素直到没有更多子元素了——这就是为什么 jsx里面render的元素必须用一个元素包裹起来的原因。
举例来说,就像下面代码:
1 | class HelloMessage extends React.Component { |
这里暂时跳过React.createElement执行过程,先了解一下React.render是如何将react这些渲染到HTML上的。
在此之前,我们先在ReactDomComponent-test.js line 42打一个断点,然后在webstorm里点调试,接下来前往src/renderers/dom/ReactDOM.js目录将光标定位到568行。点击 图标,程序将会运行到这一行并断点,这样我们可以看到createElement函数最终返回的结构。它是这样的
Render的流程
1 | ReactMount.render |
ReactMount._renderSubtreeIntoContainer
_renderSubtreeIntoContainer做了哪些事情呢?
- 参数校验:
- setState异步回调是否合法
- 目标渲染的元素是否是createElement返回的合法结构
- 是否传入了根节点容器
- 使用TopLevelWrapper包装目标渲染元素 && 设定Context预备传入
- 是否是更新环节 当前例子这里设置的null 如果更新环节需要保留一些状态传入
- 将ReactElement、根节点容器、重用标记(shouldReuseMarkup)、Context传入ReactMount._renderNewRootComponent进行渲染
ReactMount._renderNewRootComponent
它做了这些:
- 参数校验
- ReactCurrentOwner校验
- 容器合法性校验
- 记录scroll值
- 生成最终会被挂载上去的ReactNode(框架内私有表现形式,componentInstance)
- 调用ReactUpdates.batchedUpdates进行更新
ReactUpdates.batchedUpdates
它做了这些:
- 确认事务模型就绪 && batchingStrategy就绪
- 调用batchingStrategy.batchedUpdates进行下一步
- 根据isBatchingUpdates进行调度处理
- 如果为true 那么仅执行传入的callback
- 如果为false 使用transaction.perform对callback进行调用(这里有前置和收尾处理 这是事务的特点)
- 不管如何callback会被执行(这里callback是batchedMountComponentIntoNode,定义在ReactMount.js中)
batchedMountComponentIntoNode
它做了:
- 获取ReactReconcileTransaction事务实例
- 使用该事务实例调用mountComponentIntoNode来进行下一步更新
mountComponentIntoNode
这个函数的作用是挂载组件并将其渲染到DOM
它做了:
- 调用ReactReconciler.mountComponent生成markup标记内部含有{children: markup数组, node:HTMLElement}。此时mountComponent实质是ReactDomComponent.mountComponent。可以参考ReactDomComponent篇看看这个函数做了什么。
- 调用ReactMount._mountImageIntoNode渲染markup(markup.node是HTML元素可以直接插入DOM)
ReactMount._mountImageIntoNode
这个函数是最终的函数 它没有返回值,只是将相关处理好的结果插入DOM中
在这个例子时里面因为没有旧的元素而是全部新生成所以进入了
useCreateElement分支,这个分支中将会删除所有的
container子元素,并执行DOMLazyTree.insertTreeBefore
1 | if (transaction.useCreateElement) { |
而DOMLazyTree.insertTreeBefore这个函数,这个函数里面有一个比较核心的函数insertTreeChildren,这个函数会调用insertTreeChildren,insertTreeChildren又会根据传入的 tree的 node长度递归调用insertTreeChildren,总之这两个函数会互相调用。直到所有的children都被放入其对应的node,并渲染到对应容器里面。
1 | if (children.length) { |
最终,它的执行结果是 insertTreeBefore最外层获取了一个markup, 这个markup的node最终将所有的children里面的node渲染到这个markup的node节点 也就是我们最最终想要渲染的HTMLELement。获取之后,就被插入到了container里面。
最终,整个渲染就到此结束了。
TODO
- ReactReconciler.mountComponent过程是怎样的
- 事件的处理是怎样的
- 生命周期的实现