从Vue 2到Vue 3的迁移指南之破坏性特性(一、全局API)__Vue.js
发布于 4 年前 作者 banyungong 1455 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

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

全局 API <MigrationBadges :badges="$frontmatter.badges" />

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 全局 API 3.x 实例 API (应用(app))
Vue.config app.config
Vue.config.productionTip 已移除 (见下方)
Vue.config.ignoredElements app.config.isCustomElement (见下方)
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.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 实例以及它们的子孙实例中所使用。

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: DarkNin 原文链接:https://juejin.im/post/6859427370080665613

回到顶部