Vue 3源码极客之:`Vue`的`global API`:`app.use`、`app.component`等的内部实现与`app`上下文。

各位靓仔靓女们,早上好/下午好/晚上好! 今天咱们来聊聊Vue 3里那个神奇的app实例,以及围绕它展开的Global API。 保证听完之后,你对app.useapp.component等等这些API的内部运作,以及app实例的上下文,有个更清晰的认识,以后写代码也能更加自信。

咱们先来热个身,回顾一下app实例是个什么玩意儿。

app实例:Vue 3世界的入口

在Vue 2里,我们用new Vue()来创建一个根实例。但在Vue 3,我们有了更优雅的方式:createApp()

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App) // 创建一个App实例

app.mount('#app') // 挂载到id为app的元素上

这个app实例,就像是你家的大门,通过它,你可以注册全局组件、指令、插件,配置各种选项,最终控制整个Vue应用。 它的作用域是整个应用,而不是某个组件。 这跟Vue 2里直接在Vue构造函数上挂载静态方法不一样,Vue 3更强调实例的独立性。

app.use:插件的秘密通道

app.use 是一个非常常用的API,用来注册Vue插件。 插件通常用来扩展Vue的功能,比如注册全局组件、添加全局指令、提供全局方法等等。

import { createApp } from 'vue'
import App from './App.vue'

// 假设我们有一个插件 myPlugin
const myPlugin = {
  install: (app, options) => {
    // 添加全局组件
    app.component('MyComponent', {
      template: '<div>This is a global component</div>'
    })

    // 添加全局指令
    app.directive('focus', {
      mounted(el) {
        el.focus()
      }
    })

    // 添加全局方法 (挂载到 app.config.globalProperties)
    app.config.globalProperties.$myMethod = (msg) => {
      alert('Message from plugin: ' + msg)
    }

    // 打印插件的配置项
    console.log('Plugin options:', options)
  }
}

const app = createApp(App)
app.use(myPlugin, { message: 'Hello from main.js!' }) // 注册插件,并传递配置项
app.mount('#app')

那么,app.use 内部到底做了些什么呢? 让我们扒开它的源码看看:

// packages/runtime-core/src/apiCreateApp.ts
function createAppAPI<HostElement>(
  render: RootRenderFunction<HostElement>
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    const app: App<HostElement> = {
      // ... 其他属性和方法

      use(plugin: Plugin, ...options: any[]) {
        if (installedPlugins.has(plugin)) {
          warn(`Plugin has already been applied to target app.`)
          return app
        }

        if (isFunction(plugin)) {
          plugin(app, ...options)
        } else if (isObject(plugin) && isFunction(plugin.install)) {
          plugin.install(app, ...options)
        } else {
          warn(
            `A plugin must either be a function or an object with an "install" ` +
              `function.`
          )
        }

        installedPlugins.add(plugin)
        return app
      },

      // ... 其他属性和方法
    }

    return app
  }
}

简化一下,核心逻辑是这样的:

  1. 检查插件是否已经安装: installedPlugins.has(plugin) 防止重复安装。
  2. 执行插件的安装逻辑:
    • 如果插件是一个函数,直接调用 plugin(app, ...options)
    • 如果插件是一个对象,并且有 install 方法,调用 plugin.install(app, ...options)
  3. 记录已安装的插件: installedPlugins.add(plugin),防止重复安装。
  4. 返回 app 实例: 支持链式调用。

所以,插件的本质就是一个拥有 install 方法的对象,或者直接就是一个函数。 install 方法接收 app 实例作为参数,你可以在这个方法里做任何你想做的事情,比如注册组件、指令、添加全局属性等等。

app.component:全局组件的舞台

app.component 用于注册全局组件。 注册之后,你可以在任何组件的模板中使用这个组件,无需手动导入。

import { createApp } from 'vue'
import App from './App.vue'

// 注册一个全局组件
app.component('MyButton', {
  template: '<button>Click me!</button>'
})

const app = createApp(App)
app.mount('#app')

app.component 的内部实现也比较简单:

// packages/runtime-core/src/apiCreateApp.ts
function createAppAPI<HostElement>(
  render: RootRenderFunction<HostElement>
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    const context = createAppContext() // 创建一个应用上下文

    const app: App<HostElement> = {
      // ... 其他属性和方法

      component(name: string, component?: Component): any {
        if (!component) {
          return context.components[name]
        }
        if (__DEV__ && context.components[name]) {
          warn(`Component "${name}" has already been registered in the app.`)
        }
        context.components[name] = component
        return app
      },

      // ... 其他属性和方法
    }

    return app
  }
}

// packages/runtime-core/src/createApp.ts
export function createAppContext(): AppContext {
  return {
    app: null as any,
    config: {
      isNativeTag: NO,
      performance: false,
      globalProperties: {},
      optionMergeStrategies: {},
      errorHandler: undefined,
      warnHandler: undefined,
      compilerOptions: null,
      runtimeCompiler: false,
      // 2.7-only options
      transitionOptions: undefined,
      directives: {},
      components: {}, // 用于存储全局组件
    },
    mixins: [],
    components: {},
    directives: {},
    provides: Object.create(null),
    optionsCache: new WeakMap(),
    propsCache: new WeakMap(),
    emitsCache: new WeakMap(),
    renderCache: new WeakMap(),
    directivesCache: new WeakMap(),
    providesCache: new WeakMap(),
    plugin: null,
    defaults: {
      slots: {},
      props: {}
    },
    effects: [],
    reload: NOOP,
    isNativeTag: NO
  }
}

简化一下,核心逻辑是:

  1. 创建一个应用上下文 context 这个 context 包含了应用的所有全局配置和状态,比如全局组件、全局指令、全局属性等等。
  2. 将组件注册到 context.components context.components 是一个对象,用于存储全局组件,键是组件名,值是组件定义。
  3. 返回 app 实例: 支持链式调用。

当你调用 app.component('MyButton', { ... }) 时,实际上是将 MyButton 组件注册到了 context.components 中。 然后在编译模板时,Vue会从 context.components 中查找对应的组件定义。

app.directive:全局指令的魔法棒

app.directive 用于注册全局指令。 全局指令可以在任何组件的模板中使用,无需手动导入。

import { createApp } from 'vue'
import App from './App.vue'

// 注册一个全局指令
app.directive('highlight', {
  mounted(el, binding) {
    el.style.backgroundColor = binding.value
  }
})

const app = createApp(App)
app.mount('#app')

app.directive 的内部实现和 app.component 非常相似:

// packages/runtime-core/src/apiCreateApp.ts
function createAppAPI<HostElement>(
  render: RootRenderFunction<HostElement>
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    const context = createAppContext()

    const app: App<HostElement> = {
      // ... 其他属性和方法

      directive(name: string, directive?: Directive): any {
        if (!directive) {
          return context.directives[name]
        }
        if (__DEV__ && context.directives[name]) {
          warn(`Directive "${name}" has already been registered in the app.`)
        }
        context.directives[name] = directive
        return app
      },

      // ... 其他属性和方法
    }

    return app
  }
}

简化一下,核心逻辑是:

  1. 将指令注册到 context.directives context.directives 是一个对象,用于存储全局指令,键是指令名,值是指令定义。
  2. 返回 app 实例: 支持链式调用。

当你调用 app.directive('highlight', { ... }) 时,实际上是将 highlight 指令注册到了 context.directives 中。 然后在编译模板时,Vue会从 context.directives 中查找对应的指令定义。

app.config:应用的全局配置

app.config 允许你配置应用的全局行为,比如错误处理、警告处理、全局属性等等。

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 配置全局错误处理
app.config.errorHandler = (err, instance, info) => {
  console.error('Global error handler:', err, instance, info)
}

// 配置全局属性
app.config.globalProperties.$myGlobal = 'Hello from globalProperties'

app.mount('#app')

app.config 实际上指向的是 AppContext 里的 config 属性。

// packages/runtime-core/src/createApp.ts
export function createAppContext(): AppContext {
  return {
    app: null as any,
    config: {
      isNativeTag: NO,
      performance: false,
      globalProperties: {}, // 用于存储全局属性
      optionMergeStrategies: {},
      errorHandler: undefined,
      warnHandler: undefined,
      compilerOptions: null,
      runtimeCompiler: false,
      // 2.7-only options
      transitionOptions: undefined,
      directives: {},
      components: {},
    },
    mixins: [],
    components: {},
    directives: {},
    provides: Object.create(null),
    optionsCache: new WeakMap(),
    propsCache: new WeakMap(),
    emitsCache: new WeakMap(),
    renderCache: new WeakMap(),
    directivesCache: new WeakMap(),
    providesCache: new WeakMap(),
    plugin: null,
    defaults: {
      slots: {},
      props: {}
    },
    effects: [],
    reload: NOOP,
    isNativeTag: NO
  }
}
  • app.config.errorHandler:用于配置全局错误处理函数。
  • app.config.warnHandler:用于配置全局警告处理函数。
  • app.config.globalProperties:用于添加全局属性,可以在任何组件的模板中使用,比如 {{ $myGlobal }}
  • app.config.compilerOptions:用于配置编译器选项。

app.mixin:全局混入

app.mixin 允许你将一些选项混入到每个组件中。 谨慎使用,因为它会影响到所有的组件。

import { createApp } from 'vue'
import App from './App.vue'

app.mixin({
  created() {
    console.log('Mixin created!')
  }
})

const app = createApp(App)
app.mount('#app')

app.mixin 的内部实现也很简单:

// packages/runtime-core/src/apiCreateApp.ts
function createAppAPI<HostElement>(
  render: RootRenderFunction<HostElement>
): CreateAppFunction<HostElement> {
  return function createApp(rootComponent, rootProps = null) {
    const context = createAppContext()

    const app: App<HostElement> = {
      // ... 其他属性和方法

      mixin(mixin: ComponentOptions): App<HostElement> {
        if (__DEV__ && !isObject(mixin)) {
          warn(`Mixin must be an object.`)
        }
        context.mixins.push(mixin)
        return app
      },

      // ... 其他属性和方法
    }

    return app
  }
}

app.mixin 只是简单地将混入对象添加到 context.mixins 数组中。 然后在创建组件实例时,Vue会将 context.mixins 中的所有混入对象合并到组件的选项中。

app上下文:AppContext

我们一直在说app上下文,也就是 AppContext,它到底是什么? 让我们再回顾一下 AppContext 的结构:

export interface AppContext {
  app: App<any> | null
  config: AppConfig
  mixins: ComponentOptions[]
  components: Record<string, Component>
  directives: Record<string, Directive>
  provides: Record<string | symbol, any>
  optionsCache: WeakMap<ComponentOptions, InternalComponentOptions>
  propsCache: WeakMap<Component, PropObject>
  emitsCache: WeakMap<Component, ObjectEmitsOptions | null>
  renderCache: WeakMap<Component, InternalRenderFunction>
  directivesCache: WeakMap<Component, Record<string, Directive>>
  providesCache: WeakMap<Component, Record<string | symbol, any>>
  plugin: Plugin | null
  defaults: VNodeProps
  effects: ReactiveEffect[]
  reload: () => void
  /**
   * @internal
   */
  isNativeTag: (tag: string) => boolean
}
属性名 类型 描述
app App<any> | null 指向 app 实例本身。
config AppConfig 应用的全局配置,包含 errorHandlerwarnHandlerglobalProperties 等。
mixins ComponentOptions[] 全局混入对象数组。
components Record<string, Component> 全局组件注册表,键是组件名,值是组件定义。
directives Record<string, Directive> 全局指令注册表,键是指令名,值是指令定义。
provides Record<string | symbol, any> 全局 provide 对象,用于祖先组件向后代组件提供依赖。
optionsCache WeakMap<ComponentOptions, ...> 组件选项缓存。
propsCache WeakMap<Component, PropObject> 组件 props 缓存。
emitsCache WeakMap<Component, ...> 组件 emits 缓存。
renderCache WeakMap<Component, ...> 组件渲染函数缓存。
directivesCache WeakMap<Component, ...> 组件指令缓存。
providesCache WeakMap<Component, ...> 组件 provide 缓存。
plugin Plugin | null 当前已安装的插件。
defaults VNodeProps 默认的 VNode 属性。
effects ReactiveEffect[] 响应式副作用数组。
reload () => void 重新加载应用的函数 (通常用于开发环境)。
isNativeTag (tag: string) => boolean 判断是否是原生 HTML 标签的函数。

AppContext 是Vue 3应用的核心上下文,它包含了应用的所有全局配置和状态。 通过 app 实例,我们可以访问和修改 AppContext 中的属性,从而影响整个应用的行为。

总结

咱们今天一起扒了扒Vue 3 app 实例的Global API,包括 app.useapp.componentapp.directiveapp.configapp.mixin,以及背后的 AppContext。 希望通过今天的讲解,你能更深入地理解这些API的内部实现,并在实际开发中更加灵活地运用它们。

记住,app 实例是Vue 3应用的入口,通过它可以管理整个应用的状态和配置。 AppContext 则是 app 实例背后的核心上下文,包含了应用的所有全局信息。

希望这次的分享对你有所帮助! 咱们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注