搭建验证环境
这一章我们来验证前两章的分析
首先,在packages/vue/expample下面新建一个文件index.html
index.html 路径: core\packages\vue\examples\init.html
<div id="app">
<h1>{{title}}</h1>
<p>someText</p>
</div>
<script src="../dist/vue.global.js"></script>
<script>
Vue.createApp({
setup() {
return {
title: 'vue3初始化流程'
}
}
}).mount('#app')
</script>
这是一段非常简单的代码,相信大家肯定都知道这段代码在页面中会如何显示
那么现在就从这段简单的代码开始我们的验证
验证createApp
首先验证createApp
第一章中给出的结论是: 用户调用的createApp其实是调用createAppAPI方法返回的createApp方法
回想一下
- 当时是从todomvc调用的createApp出发,进入到ensureRenderer内部
- 在enusreRenderer内部其实是调用了createRenderer返回的渲染器对象的createApp方法
- 而createRenderer又返回baseCreateRenderer调用的返回值,这个返回值就是渲染器
- 渲染器内部的createApp方法是createAppAPI方法调用的返回值
- 而createAppAPI方法返回一个createApp方法
所以最后我们执行的就是这个createApp方法
因此我们第一章得出的调用栈是如此:
createApp -> ensureRenderer -> createRenderer -> baseCreateRenderer -> createAppAPI
现在我们开始打断点,将断点打在createAppAPI这里,就可以看到调用栈了 如图
路径为: core\packages\runtime-core\src\apiCreateApp.ts
刷新,看调用栈,如图
可以看到,实际的调用栈与我们从代码里看到的是一样的,所以关于createApp的调用分析是正确的
验证mount
接着验证mount
回想一下
- mount方法内部会执行render方法
- render方法内部会执行patch方法
- patch方法会执行processComponent方法
- processComponent方法会执行mountComponent方法
因此我们第一章得出的调用栈是如此:
mount -> render -> patch -> processComponent -> mountCompoent
接下来开始验证,将断点打在mountCompoent内部,如图3
路径: core\packages\runtime-core\src\renderer.ts
刷新,看调用栈,如图
可以看到,实际的调用栈与我们从代码里看到的是一样的,所以关于mount的调用分析是正确的
验证mountComponent的三个方法
接下来就是看mountComponent内部的三个方法:
- createComponentInstance 创建组件实例
- setupComponent 初始化组件实例
- setupRenderEffect 创建更新函数,机制,首次视图更新
验证createComponentInstance
这个函数就是创建instance实例,还未初始化,所有属性都是null之类的
要验证的话,可以将断点打在setupComponent这里,看看在进入setupComponent函数前的instance是什么样就知道了,如图
可以看到此时instance上的属性全是null,因此createComponentInstance确实是用来创建组件实例
验证setupComponent
验证setupCompoent
回想一下
第二章我们得出的是:
- setupComponent内部会执行setupStatefulComponent
- setupStatefulComponent执行handleSetupResult
- handleSetupResult会执行finishComponentSetup
因此我们第二章得出的调用栈是如此:
setupComponent -> setupStatefulComponent -> handleSetupResult -> finishComponentSetup
我们将断点打在finishComponentSetup内部,如图
路径: core\packages\runtime-core\src\component.ts
刷新,看调用栈,如图
可以看到执行流程是分析正确的
但是还要验证功能是否分析正确,我们之前说了经过setupComponent后,instance会有render,setupState(setup存在的情况),还有初始化了props,attrs和slots,所以应该在instance上看到他们
还是刚刚的断点,我们只要进行下一步,然后去查看instance,如图
可以看到,上面有slots render setupState等属性
注意setupState的值,如图
可以看到,title,不就是我们前面代码里面的的title吗?
因此功能分析基本正确
setupRenderEffect
验证setupRenderEffect
我们之前得出的结论是创建更新函数,建立更新机制,视图首次更新
因为更新机制涉及到响应式模块,先不管他
我们要验证的是:
- update执行的时候,是执行我们创建的更新函数componentUpdateFn去render和patch
- 并且update执行完毕后,界面渲染完毕
因此断点打在update执行的地方,如图
路径: core\packages\runtime-core\src\renderer.ts
刷新,此时我们可以看到界面是空白的,代表数据还没渲染如图
点击下一步,别点错了哦,点到下一个断点的话,页面直接刷新会让你误判的 如图
可以看到,界面渲染完毕了!如图
此时,验证了update确实是做了首次视图更新
接下来验证update的执行流程
我们之前说update是effect.run其实就是执行componentUpdateFn
猜测的调用栈:
update -> effect.run -> componentUpdateFn
因此断点打在render和patch这里如图
路径: core\packages\runtime-core\src\renderer.ts
刷新,看调用栈,如图
差别不大,run -> componentUpdateFn
其实再细分的话,patch执行完之后,视图就会更新,来看看,跳到patch断点,然后下一步,看看视图是不是更新了。
验证patch的递归调用
我们将断点打在patch中的switch(type)这里,这样就可以看看执行了多少次patch。如图
路径: core\packages\runtime-core\src\renderer.ts
一直跳到下一个断点,就可以看到,patch执行了四次,如图
结合index.html中的
<div id="app">
<h1>{{title}}</h1>
<p>someText</p>
</div>
可以看到:
- 第一次肯定是component
- 第二次是fragment(vue3中,当出现两个元素时,就会用Fragment将其包起来,这里就是包裹了h1和p,可以去看看processFragment,其实就是patchChildren)
- 第三次是h
- 第四次是p
这样四次,所有节点都已经patch完毕了
因此patch递归调用直到整个节点树渲染完毕是分析正确的
组件初始化流程
验证完前两章的分析之后,我们也可以总结一下组件初始化流程了
createApp().mount()这一句的执行流程如下图