likes
comments
collection
share

Vue3源码学习 -- 2、初始化流程分析

作者站长头像
站长
· 阅读数 40

单步调试

村长在课上介绍了一些学习源码的方法,我觉得其中的单步调试法比较适合刚开始学习源码的同学。

在搭建源码调试环境时,我们在todomvc中的createApp方法上打了一个断点,现在就从这里开始,一边调试一边看初始化的流程是如何进行的,调用了哪些方法。

应用程序实例创建过程

在调试之前,我们先给自己提两个问题。应用程序实例是如何创建的?实例长什么样?带着问题去源码中找答案,如果将这两个问题解决了,那么我们对实例的创建过程就基本了解了。

当我们刷新浏览器后,程序进入createApp方法处的断点,按F11或点击下图中的箭头进入断点,我们进入了 packages\runtime-dom\src\index.ts 文件中的createApp方法。

Vue3源码学习 -- 2、初始化流程分析

可以看到实例是通过 ensureRenderer().createApp(…args) 创建的,下面对实例中的 mount 方法进行了重写,在执行 mount 方法之前又做了一些其他的事情,然后就将实例返回了。我们想要弄清楚之前的两个问题,显然要接着进入 ensureRenderer 这个方法中,看它做了什么。

Vue3源码学习 -- 2、初始化流程分析

接着F11后,进入了 ensureRenderer 方法,这里只有一行代码,作用就是返回一个 renderer ,即渲染器。如果没有,则通过 createRenderer 方法创建一个 renderer 返回。第一次进入时 renderer 为空,则程序会进入 createRenderer 方法中,我们在该方法处打上断点,接着往下调试。

Vue3源码学习 -- 2、初始化流程分析

createRenderer 方法在 packages\runtime-core\src\renderer.ts 文件中,可以看到 createRenderer 方法是通过 baseCreateRenderer 方法返回渲染器的。这个方法是Vue3中最大的一个函数,共有2000多行,函数中做了什么,我们暂时先不关心,直接拉到函数末尾,看这个函数返回的什么。

Vue3源码学习 -- 2、初始化流程分析

在2355行,我们找到了该函数的返回结果,即上面的 renderer 。其中就有一个 createApp 方法,这个也就是上面执行 ensureRenderer 返回的createApp 。而这个函数也是通过另一个工厂函数 createAppAPI 返回的。

Vue3源码学习 -- 2、初始化流程分析

进入 packages\runtime-core\src\apiCreateApp.ts 文件中的 createAppAPI 方法中,这里终于找到了我们想要的实例 app 。

到这里,我们终于可以回答上面的两个问题了。

应用程序实例是通过 renderer 中的 createApp 方法创建的。

应用程序实例就是一个对象,里面包含了我们熟悉的 use、mixin、component、mount 等方法。

挂载过程

在创建完实例后,紧接着就调用了实例中的 mount 方法( .mount(‘#app’) ),执行挂载。调试挂载过程之前,我们依然提出一个问题:挂载都做了什么?带着这个问题继续调试下去。

Vue3源码学习 -- 2、初始化流程分析

在实例中的 mount 方法中,可以看到挂载过程做了两件事。第一件事就是创建根节点的 vnode ,第二件事就是将 vnode 传入 render 方法中,并执行。这个 render 方法是调用 createAppAPI 方法时传入的。

Vue3源码学习 -- 2、初始化流程分析

render 方法最终执行的是 patch 方法,这个方法的作用就是将传入的 vnode 转换为 dom ,并追加到宿主元素 #app 中。

首次patch过程

我们接着在 patch 方法处打上断点,继续调试首次 patch 过程。

Vue3源码学习 -- 2、初始化流程分析

进入 patch 方法中,我们将程序运行到 switch 语句处,可以看到当前的 type 是根组件,它是一个对象,会被当做组件处理。那么就会进入下面的 processComponent 方法。将断点打在该方法处。

Vue3源码学习 -- 2、初始化流程分析

继续调试,进入该方法。

Vue3源码学习 -- 2、初始化流程分析

首次执行会进入 mountComponent 方法挂载当前组件。

Vue3源码学习 -- 2、初始化流程分析

进入 mountComponent 方法后,我们将程序执行到上图中红框之后。这个 instance 就是组件实例。但它和Vue2中的不太一样,Vue2中的组件实例就是我们常用的 this 。但是Vue3中的组件实例中都是一些不认识的方法。

Vue3源码学习 -- 2、初始化流程分析

当我们再打开其中的 ctx 属性后,我们看到了一些熟悉的方法。这里才是组件的上下文。

Vue3源码学习 -- 2、初始化流程分析

再往下运行,发现执行了一个 setupComponent 方法,这个方法是对当前组件进行初始化操作。

Vue3源码学习 -- 2、初始化流程分析

如对 props 和 slots 的处理,当然最重要的是执行了 setupStatefulComponent 方法。

Vue3源码学习 -- 2、初始化流程分析

进入 setupStatefulComponent 方法后,会拿到组件中Vue3新增的Composition API中的 setup 方法。如果有,就执行。

Vue3源码学习 -- 2、初始化流程分析

继续往下可以看到,不管有没有执行 setup 方法,最终都会执行 finishComponentSetup 方法。

Vue3源码学习 -- 2、初始化流程分析

finishComponentSetup 方法主要是对模板的处理。首次进入时组件中用户没有写 render(渲染函数),则会获取当前组件的模板,通过编译函数 compile 方法生成 render ,方便之后的渲染和更新。

Vue3源码学习 -- 2、初始化流程分析

在完成了组件的初始化之后,接着执行了 setupRenderEffect 方法,该方法在之后的响应式流程中再详细了解。

Vue3源码学习 -- 2、初始化流程分析

这里我们主要关注其中1368行执行后产生的 subTree 。这个 subTree 就是当前组件的子树,就是 todomvc 页面中的 section ,和上面的根组件类似,也是一个虚拟dom。

Vue3源码学习 -- 2、初始化流程分析

Vue3源码学习 -- 2、初始化流程分析

接着往下,再次进入了 patch 方法。

Vue3源码学习 -- 2、初始化流程分析

这次 type 变成了 section ,进入 processElement 方法中。

Vue3源码学习 -- 2、初始化流程分析

首次进入 mountElement 方法。

Vue3源码学习 -- 2、初始化流程分析

在 mountElement 方法中真正创建了dom元素。但是并没有直接渲染在页面上,继续对该元素进行处理。当发现该元素还有子节点时,又会进入 mountChildren 方法中。

Vue3源码学习 -- 2、初始化流程分析

而在 mountChildren 方法中,会循环处理子元素,分别执行 patch 方法,向下递归,最终完成整棵子树的创建,再渲染到页面上。

Vue3源码学习 -- 2、初始化流程分析

总结

上面我们已经将初始化流程调试了一遍,接下来我们可以通过初始化流程的函数调用栈来总结复盘一下。

Vue3源码学习 -- 2、初始化流程分析

从下往上看,第一个是一个自调用函数,这个就是挂载,也就是 .mount(‘#app’);

然后执行了重写的方法 app.mount ; 在 app.mount 中执行了原生的 mount 方法; 

mount 中执行了将 vnode 方法转化为 dom 的 render 方法;

render 方法最终执行的是 patch 方法; 

在 patch 方法中,会通过类型判断当前是组件还是元素等; 

由于当前是根实例,所以进入了处理组件的 processComponent ;

在 mountComponent 中对组件实例进行了初始化操作; 

并且执行了 setupRenderEffect 方法,该方法为组件的更新做了一系列的处理; 

然后执行了一次更新函数, instance.update 和 run 就是 setupRenderEffect 中的 componentUpdateFn 方法; 

又一次进入 patch 方法; 

这次进入时变成了 section ,所以进入处理元素的 processElement ; 

然后创建元素,进入 mountElement ; 

如果有子元素,会进入 mountChildren 循环递归处理子元素,最终完成整棵子树的创建。

流程图:

Vue3源码学习 -- 2、初始化流程分析

转载自:https://juejin.cn/post/7203661966614986812
评论
请登录