从Vue 2到Vue 3的迁移指南之破坏性特性(一、全局API)

4,446 阅读5分钟

1、内容系个人翻译自 Vue 3.0 Beta 版文档中 Migration From Vue 2 章节
2、不一定准确,欢迎交流(别人都翻过几百遍了吧,再说也没人看)
3、水平有限、随时太监

全局 API

Vue 2.x拥有大量的全局API与配置,这些全局API与配置可以全局影响到Vue的行为方式。 例如,你可以通过 Vue.component API 创建一个全局Vue组件,如下:

Vue.component('button-counter', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">点了 {{ count }} 次。</button>'
})

类似的,这是一个全局指令的定义方式:

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

诚然这种方式很方便,但它也导致了几个问题。从技术上讲,Vue 2 并没有“应用”的概念。 我们定义的应用只是简单地通过 new Vue() 所创建的一个根Vue实例。 每一个根实例都是由同一个 Vue 构造器创建的,而它们共用相同的全局配置, 导致的结果就是:

  • 在测试的过程中,全局配置容易意外地污染到其他测试样例,用户需要小心地把源全局配置存储起来,并在每个测试样例结束之后将这些全局配置恢复至原样(例如:重置 Vue.config.errorHandler)。 有些 API 像 Vue.useVue.mixin 并没有办法逆转他们使用效果。这就导致了涉及插件的测试会特别棘手。 实际上,vue-test-utils 需要实现一个特殊的 API createLocalVue 来解决这个问题:
import { createLocalVue, mount } from '@vue/test-utils'

// 创建一个继承自 Vue 的构造器
const localVue = createLocalVue()

// 将一个插件“全局”地挂载到这个“局部”的 Vue 构造器上
localVue.use(MyPlugin)

// 再将这个 Vue 构造器传递给 mount 函数
mount(Component, { localVue })
  • 这样的特性导致我们很难在一个页面内的同个 Vue 副本的不同“应用”上使用不同的全局配置

    // 这将同时影响到两个根实例
    Vue.mixin({
      /* ... */
    })
    
    const app1 = new Vue({ el: '#app-1' })
    const app2 = new Vue({ el: '#app-2' })
    

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

全新的全局API: createApp

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

import { createApp } from 'vue'

const app = createApp({})

应用实例将暴露一个现有全局 API 的子集。大致上讲就是, 任意能全局干预到 Vue 的行为的 API 现在都被转移到了应用实例下。以下表格列举了现有的全局 API 与实例 API 的对应关系:

2.x 全局 API3.x 实例 API (应用(app))
Vue.configapp.config
Vue.config.productionTip已移除 (见下方)
Vue.config.ignoredElementsapp.config.isCustomElement (见下方)
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use (见下方)

Global API Treeshaking 中描述的那样,其余不会影响到全局行为的 API 现已作为具名 ES 模块提供给用户导入。

config.productionTip 已移除

在Vue 3.x,提示语 “use production build” 只会在当你在使用 “dev + full build” (包含了运行时+编译器及警告的构建模式)构建应用时显示。

对于 ES 模块的构建,由于它们一般与打包工具配套使用,并且在大多数情况下 CLI 或者模版文件都会将生产环境配置好,因此该提示将不再显示。

config.ignoredElements 现改为 config.isCustomElement

引入该配置项旨在支持原生的自定义元素,所以将该选项这样重命名可以更好地表达它的作用。新的选项将接收一个更加灵活的函数以取代原先的 字符串/正则表达式 方式,如:

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

// 现在
const app = Vue.createApp({})
app.config.isCustomElement = tag => tag.startsWith('ion-')

::: tip 重要

在 Vue 3.0 中,检查元素是否是一个组件的过程转移到了模版编译阶段,因此该配置项只能在运行时+编译器版本中使用。当使用仅运行时版本构建应用时,isCustomElement 必须通过构建配置传递给@vue/compiler-dom,例如: 通过 vue-loader 的 compilerOptions 选项.

  • 当使用仅运行时版本构建应用时使用了 config.isCustomElement,系统将通过一条警告信息提示用户需要构建配置传递该参数;
  • 这将是一项新的Vue CLI的顶层配置项。 :::

插件作者须知

常见的作者们导入加载插件的方法是,通过 Vue.use 自动加载插件的UMD版本。例如,以下展示了官方的 vue-router 插件在浏览器环境中的加载:

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

而在 Vue 3 中,use 这个全局API已经不再使用,该方法已经无法正常工作并在调用 Vue.use() 的时候会触发一个警告。取而代之的是,插件的使用者需要明确地在应用实例中声明插件的使用:

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

挂载应用实例

当通过 createApp(/* options */) 初始化后,应用实例 app 即可通过 app.mount(domTarget) 方式挂载到一个Vue根实例:

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

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

经过这些修改,本篇开头提及的 componentdirective 方法可以重写成这样:

const app = createApp(MyApp)

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

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

// 伴随着组件树的作用,现在每一个通过 app.mount() 挂载的应用实例都将获得相同的
// “button-counter” 组件与“focus”指令而无需污染全局环境。
app.mount('#app')

Provide / Inject

跟在 2.x 中在根实例上使用 provide 参数类似,Vue 3 的应用实例也可提供可注入到实例内的任意组件的依赖项:

// 入口处
app.provide({
  guide: 'Vue 3 Guide'
})

// 子组件处
export default {
  inject: {
    book: {
      from: guide
    }
  },
  template: `<div>{{ book }}</div>`
}

应用间共享配置

以下是一种共享配置的方式,如要创建一个共享组件或指令的工厂函数:

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')

现在 focus 这个指令即可以被 Foo 和 Bar 实例以及它们的子孙实例中所使用。