阅读vue3源码记录(二)

127 阅读6分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战

环境已经搭好,接下来,我们就顺着vue实例的创建和挂载这条主线走下去。不过,在此之前,可以先大概捋一下vue3的整体项目结构。

目录结构

image.png 可以看到,整体上是通过rollup打包 ,用ts编写,jest测试。 从文件夹上看, 大致分为packages、 scripts、 test-dts 和一些配置文件。 比如eslint的校验配置 prettier的格式化配置  。  packages中 一种有4个编译相关的包, 两个响应式的, 3个运行时相关 ,还有ssr等其他几个包和vue的业务逻辑。 scripits就是和打包关的脚本,package.json 里面的各种快捷命令刚好和这个文件夹里的文件一一对应。

scripts

例如 "dev": "node scripts/dev.js --sourcemap",,我们运行的dev 命令,实际上是用 node 去执行 scripts/dev.js 这个文件,然后,后面带上了参数。所以这个命令的关键就是dev.js这个文件。

image.png 虽然,这个配置文件只有,短短的十几行代码,但是对我这个朽木来说,还是信息量有点大

前几行都是引入函数声明计算必要变量。

execa就是一个执行函数,用于最终执行命令。

fuzzyMatchTarget也是一个工具函数,具体作用看后面。

args就是命令后面的参数 如--sourcemap. 从语法提示上可知, args是一个string数组。

target 就是最终打包的文件输出的路径。

formats 是输出格式,至于什么叫输出格式,待续。。。

commit 很明显和git 命令有关,具体内容,待续。。。

下面直接就调用了执行函数,把各种参数归位了, 整体上看有三个参数。

第一个是file实际上就是file的路径。熟悉node的话,应该一眼就能看明白。

第二个是个 数组,里面是一堆参数,其中就有我们比较关心的target、sourcemap.

最后一个是 选项对象,里面只写了一个参数,标准io库 继承。

还有test-dts测试相关的文件先不管.

packages

packages里面就是分的各种包,这些模块之间有一定的依赖关系.从vue的入口文件可以看出

image.png

我们的主包vue 依赖于响应式、编译和运行时等包。大致依赖关系如下。

image.png

先这样,后面遇到相关的逻辑再具体去看。接下来进入初始化。

初始化 createApp()

我们在使用vue的时候,初始化必然会调用Vue.createApp() ,故我们的断点也是打在这里。在vscode中,也可以直接点进去,如果是js文件的话。可惜demo里面的都是一个html. 不过,我们已经通过打断点的方式,知道了,这个createApp()的位置,和大概执行轨迹。

我们首先进入到了 packages\runtime-dom\src\index.ts,这里的67行,显然这个createApp函数是在这里被定义的。

image.png; 大致上看,这个函数就做了两件事,第一,又调用了两个方法传入了参数生成了一个app对象;第二, 在这个app对象上解构出一个mount 方法,然后给app一个新的mount方法。最后返回这个app,这也是可以链式编程的原因,并且mount方法是是在app创建之后又重新赋值上去的,并不是app实例化就有了的那个,有点费解。可以先记下这个小问题。

想知道app到底怎么实例化,我们就再看看刚才略过去的两个函数 ensureRenderer().createApp.

ensureRenderer返回一个实例对象renderer。而这个对象上又有一个createApp方法,这个才是我们要重点关注的,因为参数都传到这个函数里了。继续断点, 我们就进入到了packages\runtime-core\src\apiCreateApp.ts 的177行。 这一个再没有嵌套了,这个函数就是在这里声明的。终于找到createApp的源头 了!

image.png 这个函数整体上来说,就只做了一件事,实例化一个app并返回它。实例化的过程就是给它添加各种属性和方法,先不一一细看,那是支线。

属性

      _uid: uid++,
      _component: rootComponent as ConcreteComponent,
      _props: rootProps,
      _container: null,
      _context: context,
      _instance: null,

方法

image.png

似乎不见了我们熟悉的data computed watch这些,另外component use minxin directive 这些方法直接挂在了实例上。 我们不妨直接打印一下这个app看看到底还有什么。另外,我们在断点调试的过程中也是可以直接查看

image.png

初始化,粗略来看就这么多。就是调用renderer.createApp() 返回实例,然后对实例的mount方法再处理,返回实例。

挂载 app.mount()

挂载的方法,上面已经看到了,就是在调用renderer.createApp() 返回实例的基础上,再处理。

先是检查容器的有效性, 也就是挂载目标元素。

然后,检查渲染函数。如果没有自定义渲染函数或者模板,就先把实例的innerhtml初始化为容器的innerHtml.

后面还有一个条件判断,看上去是处理容器的属性。

置空容器的内部内容, 关于置空,我能想到的就是和v-if有关。

最后,调用原来的mount得到一个代理,返回它。

看来,想搞明白mount到底做了什么,还得看初始化的mount方法长啥样

app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    const container = normalizeContainer(containerOrSelector)
    if (!container) return

    const component = app._component
    if (!isFunction(component) && !component.render && !component.template) {
      component.template = container.innerHTML
      // 2.x compat check
      if (__COMPAT__ && __DEV__) {
        for (let i = 0; i < container.attributes.length; i++) {
          const attr = container.attributes[i]
          if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
            compatUtils.warnDeprecation(
              DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
              null
            )
            break
          }
        }
      }
    }
    // clear content before mounting
    container.innerHTML = ''
    const proxy = mount(container, false, container instanceof SVGElement)
    if (container instanceof Element) {
      container.removeAttribute('v-cloak')
      container.setAttribute('data-v-app', '')
    }
    return proxy
  }
mount

这个mount方法 需要三个参数, 容器, isHydrate, 是否svg. 这个isHydrate到底什么用的,先来满足一下好奇心,看了一下,还是不明白,反正默认是false,先不管。 那么正常的mount实际上,

如果未执行mount, 就创建对应的Vnode,这不就说明,vnode只会在组件初始挂载的时候创建一次,后续都是修改这个Vnode。

然后就执行了render函数,下面的就只是把状态改为已挂载,和一些属性挂载。所以mount的关键就在render里面了

render

进入render函数,我们就发现,在有vnode的情况下,又调用了两个函数 patch() 和 flushPostFlushCbs(), 但是接受参数的是patch ,因此,现在主要关心的就是patch,另一个可以,稍微瞄一眼。

patch

恨明显,这里就是VNode diff的地方,因为第一行代码就是比较,相等就直接返回了。 再往下看,似乎就是一些各种判断,判断节点类型,从而进行不同的操作。已接近我的完全空白区了。

不过,可以看到多种情况都会调用一个对应的process函数。

text 文本

comment 注释

component 组件

fragment 片段

element 元素

这些函数内部应该就是 ,把VNode编译为对应的html节点,并且挂到目标容器。

这些函数内部都调用了一个 hostInsert 函数。这个hostInsert函数的参数就有容器。既然有,肯定会用得上

这一次的记录,暂时就到这里。

未完待续。。。。。。