Vue3源码解析 -- 2.整体梳理与初始化

基于vue@3.3.4源码

整体梳理

Vue 导出了什么

上一节我们知道了,Vue3 打包时入口文件就是 packages/vue/src/runtime.ts 或者 packages/vue/src/index.ts,那我们就看下这两个文件的源码。

先看 packages/vue/src/index.ts,相当于导出了 compile 方法,又导出了 @vue/runtime-dom 包中的所有内容。

js 复制代码
function compileToFunction(template, options) {
  // ...
}

export { compileToFunction as compile };
export * from '@vue/runtime-dom';

再看 packages/vue/src/runtime.ts 文件,同样是导出了 @vue/runtime-dom 包的所有内容和 compile 方法,但是 compile 方法实际上没有任何作用。这也证明,其实整个的 Vue,相比 runtime 的,就是多了 compile 的内容。

js 复制代码
export * from '@vue/runtime-dom';

export const compile = () => {
  // ...
};

@vue/runtime-dom 导出了什么

我们再看 @vue/runtime-dom 都导出了什么,这个顾名思义,在 packages/runtime-dom/src/index.ts 文件中。

可以看到,导出了很多全局方法,比如 createApp,还导出了一些全局组件和全局指令。然后是导出了 @vue/runtime-core 包中的所有内容,和 jsx 文件中的所有内容,jsx 中全是针对 jsx 写法的 ts 类型。

js 复制代码
export const createApp = (...args) => {};

// DOM-only components
export { Transition } from './components/Transition';
export { TransitionGroup } from './components/TransitionGroup';

// DOM-only runtime directive helpers
export { withModifiers } from './directives/vOn';
export { vShow } from './directives/vShow';

// re-export everything from core
// h, Component, reactivity API, nextTick, flags & types
export * from '@vue/runtime-core';

export * from './jsx';

@vue/runtime-core 导出了什么

然后我们接着分析 @vue/runtime-core 包中都导出了什么,在 packages/runtime-core/src/index.ts 文件中

可以看到,这个把 Vue3 的所有核心和高级的 api 全部导出来了,同时也导出了很多定义好的 ts 类型。具体 api 作用可看官网中的介绍:cn.vuejs.org/api/

js 复制代码
// Core API ------------------------------------------------------------------
export {
  // 核心
  reactive,
  ref,
  readonly,
  // 工具
  unref,
  isRef,
  toRef,
  toValue,
  toRefs,
  isProxy,
  isReactive,
  isReadonly,
  // 进阶
  customRef,
  triggerRef,
  shallowRef,
  shallowReactive,
  shallowReadonly,
  markRaw,
  toRaw,
  effectScope,
  getCurrentScope,
  onScopeDispose,
} from '@vue/reactivity';
// 计算属性
export { computed } from './apiComputed';
// 观察者
export { watch, watchEffect, watchPostEffect, watchSyncEffect } from './apiWatch';
// 生命周期
export {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onActivated,
  onDeactivated,
  onErrorCaptured,
} from './apiLifecycle';
// 注入
export { provide, inject } from './apiInject';
export { nextTick } from './scheduler';
export { defineComponent } from './apiDefineComponent';
export { defineAsyncComponent } from './apiAsyncComponent';

// <script setup> API ----------------------------------------------------------
export {
  // 只能在 <script setup> 中使用的编译器宏,用于类型检查和警告
  defineProps,
  defineEmits,
  defineExpose,
  defineOptions,
  defineSlots,
  useAttrs,
  useSlots,
} from './apiSetupHelpers';

// Advanced API ----------------------------------------------------------------
// setup 函数中不能使用 this。如果确实需要使用,可以用 const { proxy } = getCurrentInstance();获取实例
export { getCurrentInstance } from './component';
// 创建 vnodes
export { h } from './h';
// vnodes的工具方法
export { cloneVNode, mergeProps, isVNode } from './vnode';
// Built-in components
export { Teleport } from './components/Teleport';
export { Suspense } from './components/Suspense';
export { KeepAlive } from './components/KeepAlive';
// For using custom directives
export { withDirectives } from './directives';

// Custom Renderer API ---------------------------------------------------------
export { createRenderer } from './renderer';

// Types -------------------------------------------------------------------------
// ...

总结

综上所述,从上面的分析中,可以看到在入口文件中把 Vue3 的所有的 api 方法全部导出来了。

跟 Vue2 不同,Vue2 是导出一个 Vue 类,Vue3 是导出方法,使用时要 import 这些方法:

js 复制代码
import { createApp, nextTick, ref, computed, watch, provide, inject, onMounted } from 'vue';

Vue 初始化

Vue3 中通过 createApp() 来创建整个 app 实例,app 实例上可以使用其他插件,比如 router 和其他组件库等。app.config 是对这个 app 实例的配置,比如全局错误处理等。

最后,通过 mount() 方法将 app 实例挂载在一个容器元素中。

看下面的例子。

js 复制代码
const app = createApp(App);
app.use(createPinia());
app.config.errorHandler = handleError;

app.mount('#demo');

createApp

接下来我们一步步分析这些源码是怎么实现的,先来看 createApp,它内部调用了 ensureRenderer().createApp(),也就是 ensureRenderer 最终会返回一个 createApp 方法。

之后重写了 mount 方法。

js 复制代码
const createApp = (...args) => {
  const app = ensureRenderer().createApp(...args);
  const { mount } = app;
  app.mount = (containerOrSelector) => {
    // ...
  };

  return app;
};

baseCreateRenderer

再看 ensureRenderer 方法,它最终调用了 baseCreateRenderer 方法。

baseCreateRenderer 里面很多方法我们先忽略,最终就是创建一个对象并返回,对象中包含 createApp 方法。这个方法会调用 createAppAPI。

js 复制代码
function ensureRenderer() {
  return createRenderer(rendererOptions);
}

function createRenderer(options) {
  return baseCreateRenderer(options);
}

function baseCreateRenderer(options) {
  // ...
  return {
    render,
    createApp: createAppAPI(render),
  };
}

createAppAPI

接着看 createAppAPI,它直接返回了一个函数,名字也是 createApp,这个方法才是真正创建实例的地方。它创建了一个 context 对象和 app 对象,最后返回 app 对象。

app 对象是一个应用程序实例,用于组织和配置应用程序的各个部分,包括注册组件、指令、挂载根组件等。它是开发者与应用程序进行交互的接口。

context 对象是一个内部的上下文对象,它用于在 Vue3 内部管理应用程序的全局状态、配置和插件。可以用于存储全局状态,这些状态可能会被多个组件或指令共享。

js 复制代码
function createAppAPI(render) {
  // 这里才是真正的创建app实例的地方
  return function createApp(rootComponent, rootProps = null) {
    // 全局的app上下文,里面包含全局组件,全局指令,mixins,config,以及app实例等
    const context = createAppContext();
    const installedPlugins = new Set();
    const app = {
      _uid: uid++,
      _component: rootComponent,
      _props: rootProps,
      _container: null,
      _context: context,
      _instance: null,

      get config() {
        return context.config;
      },

      // 注册插件
      use(plugin, ...options) {
        installedPlugins.add(plugin);
        plugin.install(app, ...options);
        return app;
      },

      // 这些方法将mixin,component,directive都塞到context的属性中
      mixin(mixin) {
        if (!context.mixins.includes(mixin)) {
          context.mixins.push(mixin);
        }
        return app;
      },
      component(name, component) {
        if (!component) {
          return context.components[name];
        }
        context.components[name] = component;
        return app;
      },
      directive(name, directive) {
        if (!directive) {
          return context.directives[name];
        }
        context.directives[name] = directive;
        return app;
      },
      provide(key, value) {
        context.provides[key] = value;
        return app;
      },

      // 挂载和卸载
      mount() {},
      unmount() {},
    };

    return app;
  };
}

createAppContext 用来创建一个全局的上下文对象,这个对象会挂在每一个子组件对象上。以供所有的地方都能使用。

js 复制代码
function createAppContext() {
  return {
    app: null,
    config: {
      isNativeTag: NO,
      performance: false,
      globalProperties: {},
      optionMergeStrategies: {},
      errorHandler: undefined,
      warnHandler: undefined,
      compilerOptions: {},
    },
    mixins: [],
    components: {},
    directives: {},
    provides: Object.create(null),
    optionsCache: new WeakMap(),
    propsCache: new WeakMap(),
    emitsCache: new WeakMap(),
  };
}

总结

我们通过 const app = createApp(App); 创建了一些 app 对象,这个 app 对象提供了一些全局方法,比如注册组件、指令、挂载根组件等,也可以设置 config 对象,app 中的 config 对象就是 context 中的 config。

最后通过 mount 方法将应用挂载并渲染到页面。

相关推荐
魔术师卡颂7 小时前
如何让“学源码”变得轻松、有意义
前端·面试·源码
Java指南修炼2 天前
一个开源的大语言模型(LLM)服务工具,支持Llama 3.1、Phi 3、Mistral、Gemma 2 等, 87.4k star你必须拥有(附源码)
人工智能·后端·语言模型·开源·源码
一 乐3 天前
英语学习交流平台|基于java的英语学习交流平台系统小程序(源码+数据库+文档)
java·数据库·vue.js·学习·小程序·源码
IT毕设梦工厂5 天前
计算机毕业设计选题推荐-推拿知识互动平台-Java/Python项目实战
java·spring boot·python·django·毕业设计·源码·课程设计
工业互联网专业5 天前
基于ssm+vue+uniapp的智能停车场管理系统小程序
vue.js·小程序·uni-app·毕业设计·ssm·源码·课程设计
IT毕设梦工厂6 天前
计算机毕业设计选题推荐-勤工俭学兼职系统-助学兼职系统-Java/Python项目实战(亮点:手机验证码验证+数据可视化)
java·spring boot·python·django·毕业设计·源码·课程设计
一 乐6 天前
在线小说|基于java的小说阅读系统小程序(源码+数据库+文档)
java·开发语言·数据库·小程序·源码
IT毕设梦工厂6 天前
计算机毕业设计选题推荐-项目评审系统-Java/Python项目实战
java·spring boot·python·django·毕业设计·源码·课程设计
IT研究室7 天前
计算机毕业设计选题推荐-校园车辆管理系统-Java/Python项目实战(亮点:数据可视化分析、账号锁定)
java·spring boot·python·django·毕业设计·源码·课程设计