学习 vue3 源码,是不是一时不知道从哪里入手?不了解哪些有依赖关系,哪些能做什么事?今天我们就 vue 项目的吃实话流程进行分析。
(一) 整体结构
vue3 的源码中,其根目录只有两个核心的目录: packages 与 scripts。scripts 只要用于一些工程操作,比如检查环境、打包等,而真正的源码都位于 packages 目录下。
- reactivity : 响应式API,例如toRef、reactive、Effect、computed、watch等,可作为与框架无关的包,独立构建。
- runtime-core : 平台无关的运行时核心代码。包括虚拟dom渲染、组件实现和JavaScript API。可以使用这个包针对特定平台构建高价运行时(即定制渲染器)。
- runtime-dom : 针对浏览器的运行时。包括对原生DOM API、属性(attributes)、特性(properties)、事件回调的处理。
- runtime-test : 用于测试的轻量级运行时。可以在任何JavaScript环境使用,因为它最终只会呈现JavaScript对象形式的渲染树,其可以用来断言正确的渲染输出。另外还提供用于序列化树、触发事件和记录更新期间执行的实际节点操作的实用工具。
- server-renderer : 服务端渲染相关。
- compiler-core : 平台无关的编译器核心代码。包括编译器可扩展基础以及与所有平台无关的插件。
- compiler-dom : 添加了针对浏览器的附加插件的编译器。
- compiler-sfc : 用于编译Vue单文件组件的低阶工具。
- compiler-ssr : 为服务端提供优化后的渲染函数的编译器。
- template-explorer : 用于调试编译器输出的开发者工具。运行nr dev template-explorer命令后打开它的index.html文件,获取基于当前源代码的模板的编译结果。也可以使用在线版本live version
- shared : 多个包共享的内部工具(特别是运行时包和编译器包所使用的与环境无关的工具)。
- vue : 用于面向公众的完整构建,其中包含编译器和运行时。
上述包中,剔除服务端渲染、开发调试、测试等相关代码,它们的依赖关系大致时这样的:
(二)初始化流程分析
我们今天要研究的初始化流程主线包括了实例创建过程(createApp)与挂载过程(app.mount())。
这里我们使用上一篇介绍的调试环境中介绍的单步调试方法:
【vue3源码学习】如何开始源码学习及调试环境的安装?
通过单步调试方法,我们来分析 createApp 与 mount 的实现。
createApp
从 createApp 出发进入 ensureRenderer() 方法
当使用createApp创建应用实例时,会首先调用一个ensureRenderer方法。
ensureRenderer函数会返回一个渲染器renderer,这个renderer是个全局变量,如果不存在,会使用createRenderer方法进行创建,并将创建好的renderer赋值给这个全局变量。
在源码文件中的路径:packages\runtime-dom\src\index.ts
而createRenderer函数中会调用baseCreateRenderer函数,并返回其结果。
这个返回值就是渲染器:
return {
render, // 把接收到的 vnode 转换成 DOM,追加到宿主元素
hydrate, // SSR ,服务端将 vnode 生成为 html
createApp: createAppAPI(render, hydrate) // 创建 App 实例
}
渲染器是什么
由上边的代码可以看出,渲染器就是一个包含 render、hydrate、createApp 的对象。
他的作用就是:
- 实现首次渲染
- 获取应用实例
我们可以看到真正初始化的核心方法是 createAppAPI(render, hydrate)
createAppAPI 又返回了一个 createApp 方法,而这个方法才是我们看到的 vue 实例的真实样子。我们平常熟悉的config、use、mixin、mount 等重要的方法都在这里。
在源码文件中的路径:packages\runtime-core\src\apiCreateApp.ts
敲黑板:此处是重点 我们会发现 vue2 与 vue3 创建实例的方法是有很大不同的:
.
..........................................................................
.
vue2
vue2 的所有 Vue 实例是共享一个 Vue 构造函数对象的,包括全局指令/全局组件,无法做到项目隔离。也就是说整个项目中,只有一个根 Vue 实例,其他的单文件组件创建的 Vue 实例都会成为它的子实例。import Vue from 'Vue' import APP from './APP.vue' const vm = new Vue({ render:h => h(APP) }) vm.$mount('#app')
vue3
而 vue3 是通过 createApp 工厂函数来创建实例的,通过createApp方法可以返回一个提供应用上下文的应用实例,不同实例注册的组件无法在不同的实例下使用。import {createApp} from 'vue' import APP from './APP.vue' const app = createApp(APP) app.mount('#app')
下期预告:
挂载都做了什么?
- 挂载只执行一次
- 初始化 => 看到渲染过程
- 建立更新机制 => 不停循环