学Vue3源码之初始化加挂载(第一篇)
最近受到阿崔cxr老师和杨村长老师的影响,开始跟随学习许多前端技术,他们的教学视频满满的干货,尤其是即将转为默认版本的Vue3更是值得一学,所以产生学习Vue3源码的想法,初学源码一定有很多困惑,希望自己能坚持下去。
首先是通过git克隆代码Vue3源码 github.com/vuejs/core ,安装完以后需要使用pnpm install安装依赖。 然后需要在package.json文件里修改dev运行脚本,添加 --sourcemap。
接着会在 /packages/vue/ 目录中生成dist目录
然后在/vue/examples/ 目录创建一个test01.html,将一下代码写入
<script src="../dist/vue.global.js"></script>
<div id="app">
<section>
<div>{{ count }}</div>
<button @click="addCount">add</button>
</section>
</div>
<script>
const { createApp, ref } = Vue
var app = createApp({
setup() {
let count = ref(1)
const addCount = () => {
count.value++
}
return {
count,
addCount
}
}
})
app.mount('#app')
console.log(app.config.compilerOptions);
</script>
首先在var app = createApp处打断点然后刷新页面然后按F11进入此createApp方法。 这时js执行到了packages/runtime-dom/src/index.ts中的createApp函数
由此可见createApp是调用了ensureRenderer().createApp()。 在78行代码处打上断点继续F11跳进ensureRenderer()函数。
这里的rendererOptions包括了:(个人理解)
这里可以看出ensureRenderer()函数是返回了一个生成渲染器的函数createRenderer()的执行结果,按F11进入这个函数。
这时js执行到了vue的核心模块/packages/runtime-core/目录中的renderer.ts文件
在这个函数中又返回名叫baseCreateRenderer方法的返回结果。按F11进入这个函数看看都做了什么。
这个函数一共有2000+行,可见这个函数做了很多事情,具体信息以后再详细记录,这里只看最后的return输出了什么东西。
该函数输出了一个将vnode转换成真是dom的render函数,
和一个由生成createAppAPi()方法生成的函数作用到createApp上。
这里可以进入到这个createAppAPI()函数中。
这里看到了原来我在调用createApp()方法创建app实例时用得是这个返回的createApp()
看到这里恍然大悟,我粗略的看了一遍该函数做了那些事情。
原来这个函数返回了一个对象来作为app实例对象。 其中包括了全局配置的config属性、使用插件的use()方法、mixin混入、directive自定义指令、mount()函数等。
看到这个mount()函数后,一开始我以为在调用mount()函数时只是直接调用了这个函数。但是菜鸡的我还是大意了。。我返回到断点最初进入的代码片段发现了他对mount()做了封装,代码如下:
我们解构出来的mount方法被用在了代码127行所调用,再来看看这个mount函数:
这里我觉得跟挂载相关的函数应该是
/**
* 生成虚拟dom
* rootComponent 初始化时为根组件
* rootProps 给根组件传如的props
*/
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// 这里的render方法是调用了之前baseCreateRenderer()函数创建的render()函数
render(vnode, rootContainer, isSVG)
我们这时又回到了render()函数
// 渲染函数
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
// 参数1为老vnode 参数2为新vnode 参数3为挂载节点
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
flushPostFlushCbs()
// 每次patch后 新节点会追加到挂载父节点上的_vnode上作为下次使用的老节点
container._vnode = vnode
}
由于是初始化并且生成了根组件的vnode,所以会走patch方法来生成真实dom。
目前只阅读到了这里,其中很多细节内容还待研读,读到这里已经是怀疑人生,一脸懵逼的状态了。不过,还是会继续努力看下去的。
最后再次感谢杨村长和崔大两位老师(大佬)。看过两位大佬的课后,觉得自己有了目标,也有了学习的动力,之后会继续支持你们。