学Vue3源码之初始化加挂载(第一篇)

467 阅读3分钟

学Vue3源码之初始化加挂载(第一篇)

最近受到阿崔cxr老师和杨村长老师的影响,开始跟随学习许多前端技术,他们的教学视频满满的干货,尤其是即将转为默认版本的Vue3更是值得一学,所以产生学习Vue3源码的想法,初学源码一定有很多困惑,希望自己能坚持下去。

首先是通过git克隆代码Vue3源码 github.com/vuejs/core ,安装完以后需要使用pnpm install安装依赖。 然后需要在package.json文件里修改dev运行脚本,添加 --sourcemap。

image.png

接着会在 /packages/vue/ 目录中生成dist目录

image.png

然后在/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函数

image.png

由此可见createApp是调用了ensureRenderer().createApp()。 在78行代码处打上断点继续F11跳进ensureRenderer()函数。

image.png

这里的rendererOptions包括了:(个人理解)

image.png

这里可以看出ensureRenderer()函数是返回了一个生成渲染器的函数createRenderer()的执行结果,按F11进入这个函数。

这时js执行到了vue的核心模块/packages/runtime-core/目录中的renderer.ts文件

image.png

在这个函数中又返回名叫baseCreateRenderer方法的返回结果。按F11进入这个函数看看都做了什么。

image.png

这个函数一共有2000+行,可见这个函数做了很多事情,具体信息以后再详细记录,这里只看最后的return输出了什么东西。

image.png 该函数输出了一个将vnode转换成真是dom的render函数, 和一个由生成createAppAPi()方法生成的函数作用到createApp上。

这里可以进入到这个createAppAPI()函数中。

image.png 这里看到了原来我在调用createApp()方法创建app实例时用得是这个返回的createApp() 看到这里恍然大悟,我粗略的看了一遍该函数做了那些事情。

image.png

原来这个函数返回了一个对象来作为app实例对象。 其中包括了全局配置的config属性、使用插件的use()方法、mixin混入、directive自定义指令、mount()函数等。

看到这个mount()函数后,一开始我以为在调用mount()函数时只是直接调用了这个函数。但是菜鸡的我还是大意了。。我返回到断点最初进入的代码片段发现了他对mount()做了封装,代码如下:

image.png 我们解构出来的mount方法被用在了代码127行所调用,再来看看这个mount函数:

image.png

这里我觉得跟挂载相关的函数应该是

/**
 * 生成虚拟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。

目前只阅读到了这里,其中很多细节内容还待研读,读到这里已经是怀疑人生,一脸懵逼的状态了。不过,还是会继续努力看下去的。

最后再次感谢杨村长和崔大两位老师(大佬)。看过两位大佬的课后,觉得自己有了目标,也有了学习的动力,之后会继续支持你们。