# 全局 API
非兼容

Vue 2.x 有许多全局 API 和配置,这些 API 和配置可以全局改变 Vue 的行为。例如,要注册全局组件,可以使用 Vue.component 这样的 API:

Vue.component('button-counter', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})
1
2
3
4
5
6

类似地,使用全局指令的声明方式如下:

Vue.directive('focus', {
  inserted: el => el.focus()
})
1
2
3

虽然这种声明方式很方便,但它也会导致一些问题。从技术上讲,Vue 2 没有“app”的概念,我们定义的应用只是通过 new Vue() 创建的根 Vue 实例。从同一个 Vue 构造函数创建的每个根实例共享相同的全局配置,因此:

  • 在测试期间,全局配置很容易意外地污染其他测试用例。用户需要仔细存储原始全局配置,并在每次测试后恢复 (例如重置 Vue.config.errorHandler)。有些 API 像 Vue.use 以及 Vue.mixin 甚至连恢复效果的方法都没有,这使得涉及插件的测试特别棘手。实际上,vue-test-utils 必须实现一个特殊的 API createLocalVue 来处理此问题:

    import { createLocalVue, mount } from '@vue/test-utils'
    
    // 建扩展的 `Vue` 构造函数
    const localVue = createLocalVue()
    
    // 在 “local” Vue构造函数上 “全局” 安装插件
    localVue.use(MyPlugin)
    
    // 通过 `localVue` 来挂载选项
    mount(Component, { localVue })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  • 全局配置使得在同一页面上的多个“app”之间共享同一个 Vue 副本非常困难,但全局配置不同。

    // 这会影响两个根实例
    Vue.mixin({
      /* ... */
    })
    
    const app1 = new Vue({ el: '#app-1' })
    const app2 = new Vue({ el: '#app-2' })
    
    1
    2
    3
    4
    5
    6
    7

为了避免这些问题,在 Vue 3 中我们引入...

# 一个新的全局 API:createApp

调用 createApp 返回一个应用实例,这是 Vue 3 中的新概念:

import { createApp } from 'vue'

const app = createApp({})
1
2
3

如果你使用的是 Vue 的 CDN 构建,那么 createApp 是通过全局的 Vue 对象暴露的。

const { createApp } = Vue

const app = createApp({})
1
2
3

应用实例暴露了 Vue 2 当前全局 API 的子集,经验法则是,任何全局改变 Vue 行为的 API 现在都会移动到应用实例上,以下是当前 Vue2 全局 API 及其相应实例 API 的表:

2.x 全局 API 3.x 实例 API (app)
Vue.config app.config
Vue.config.productionTip removed (见下方)
Vue.config.ignoredElements app.config.isCustomElement (见下方)
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use (见下方)
Vue.prototype app.config.globalProperties (见下方)

所有其他不全局改变行为的全局 API 现在被命名为 exports,文档见全局 API Treeshaking

# config.productionTip 移除

在 Vue 3.x 中,“使用生产版本”提示仅在使用“dev + full build”(包含运行时编译器并有警告的构建) 时才会显示。

对于 ES 模块构建,由于它们是与 bundler 一起使用的,而且在大多数情况下,CLI 或样板已经正确地配置了生产环境,所以本技巧将不再出现。

# config.ignoredElements 替换为 config.isCustomElement

引入此配置选项的目的是支持原生自定义元素,因此重命名可以更好地传达它的功能,新选项还需要一个比旧的 string/RegExp 方法提供更多灵活性的函数:

// 之前
Vue.config.ignoredElements = ['my-el', /^ion-/]

// 之后
const app = createApp({})
app.config.isCustomElement = tag => tag.startsWith('ion-')
1
2
3
4
5
6

重要

在 Vue 3 中,元素是否是组件的检查已转移到模板编译阶段,因此只有在使用运行时编译器时才考虑此配置选项。如果你使用的是 runtime-only 版本 isCustomElement 必须通过 @vue/compiler-dom 在构建步骤替换——比如,通过 compilerOptions option in vue-loader (opens new window)

  • 如果 config.isCustomElement 当使用仅运行时构建时时,将发出警告,指示用户在生成设置中传递该选项;
  • 这将是 Vue CLI 配置中新的顶层选项。

# Vue.prototype 替换为 config.globalProperties

在 Vue 2 中, Vue.prototype 通常用于添加所有组件都能访问的 property。

在 Vue 3 等同于config.globalProperties。这些 property 将被复制到应用中作为实例化组件的一部分。

// 之前 - Vue 2
Vue.prototype.$http = () => {}
1
2
// 之后 - Vue 3
const app = createApp({})
app.config.globalProperties.$http = () => {}
1
2
3

使用 provide (稍后会讨论) 时,也应考虑作为 globalProperties 的替代品。

# 插件使用者须知

插件开发者通常使用 Vue.use。例如,官方的 vue-router 插件是如何在浏览器环境中自行安装的:

var inBrowser = typeof window !== 'undefined'
/* … */
if (inBrowser && window.Vue) {
  window.Vue.use(VueRouter)
}
1
2
3
4
5

由于 use 全局 API 在 Vue 3 中不再使用,此方法将停止工作并停止调用 Vue.use() 现在将触发警告,于是,开发者必须在应用程序实例上显式指定使用此插件:

const app = createApp(MyApp)
app.use(VueRouter)
1
2

# 挂载 App 实例

使用 createApp(/* options */) 初始化后,应用实例 app 可用 app.mount(domTarget) 挂载根组件实例:

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

const app = createApp(MyApp)
app.mount('#app')
1
2
3
4
5

经过所有这些更改,我们在指南开头的组件和指令将被改写为如下内容:

const app = createApp(MyApp)

app.component('button-counter', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})

app.directive('focus', {
  mounted: el => el.focus()
})

// 现在所有应用实例都挂载了,与其组件树一起,将具有相同的 “button-counter” 组件 和 “focus” 指令不污染全局环境
app.mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Provide / Inject

与在 2.x 根实例中使用 provide 选项类似,Vue 3 应用实例还可以提供可由应用内的任何组件注入的依赖项:

// 在入口
app.provide('guide', 'Vue 3 Guide')

// 在子组件
export default {
  inject: {
    book: {
      from: 'guide'
    }
  },
  template: `<div>{{ book }}</div>`
}
1
2
3
4
5
6
7
8
9
10
11
12

使用 provide 在编写插件时非常有用,可以替代 globalProperties

# 在应用之间共享配置

在应用之间共享配置 (如组件或指令) 的一种方法是创建工厂功能,如下所示:

import { createApp } from 'vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'

const createMyApp = options => {
  const app = createApp(options)
  app.directive('focus', /* ... */)

  return app
}

createMyApp(Foo).mount('#foo')
createMyApp(Bar).mount('#bar')
1
2
3
4
5
6
7
8
9
10
11
12
13

现在,FooBar 实例及其后代中都可以使用 focus 指令。