Vue3 初探(三)全局API
Vue2时代的全局API全部都是挂载在Vue原型上的,所以任何对全局API的修改都会作用到所有的Vue实例。这可能是我们所不希望的,同时这种修改又是不可逆的。
所以为了应对这种情况,Vue3将全局API改造,引入了APP的概念,每个实例都是一个APP,全局API是作用在APP上的。
Vue 2.x 有许多全局 API 和配置,这些 API 和配置可以全局改变 Vue 的行为。例如,要创建全局组件,可以使用 Vue.component
这样的 API:
1 | Vue.component('button-counter', { |
类似地,使用全局指令的声明方式如下:
1 | Vue.directive('focus', { |
虽然这种声明方式很方便,但它也会导致一些问题。
从技术上讲,Vue 2 没有“app”的概念,我们定义的应用只是通过 new Vue()
创建的根 Vue 实例。从同一个 Vue 构造函数创建的每个根实例共享相同的全局配置,因此:
- 在测试期间,全局配置很容易意外地污染其他测试用例。用户需要仔细存储原始全局配置,并在每次测试后恢复 (例如重置
Vue.config.errorHandler
)。有些 API 像Vue.use
以及Vue.mixin
甚至连恢复效果的方法都没有,这使得涉及插件的测试特别棘手。实际上,vue-test-utils 必须实现一个特殊的 APIcreateLocalVue
来处理此问题:
1 | import { createLocalVue, mount } from '@vue/test-utils' |
全局配置使得在同一页面上的多个“app”之间共享同一个 Vue 副本非常困难,但全局配置不同。
1
2
3
4
5
6
7// 这会影响两个根实例
Vue.mixin({
/* ... */
})
const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })
为了避免这些问题,在 Vue 3 中我们引入…
一个新的全局 API:createApp
调用 createApp
返回一个应用实例,这是 Vue 3 中的新概念:
1 | import { createApp } from 'vue' |
应用实例暴露当前全局 API 的子集,经验法则是,任何全局改变 Vue 行为的 API 现在都会移动到应用实例上,以下是当前全局 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 (见下方) |
所有其他不全局改变行为的全局 API 现在被命名为 exports
插件使用者须知
插件开发者通常使用 Vue.use
。例如,官方的 vue-router
插件是如何在浏览器环境中自行安装的:
1 | var inBrowser = typeof window !== 'undefined' |
由于 use
全局 API 在 Vue 3 中不再使用,此方法将停止工作并停止调用 Vue.use()
现在将触发警告,于是,开发者必须在应用程序实例上显式指定使用此插件:
1 | const app = createApp(MyApp) |
挂载 App 实例
使用 createApp(/* options */)
初始化后,应用实例 app
可用于挂载具有 app.mount(domTarget)
:
1 | import { createApp } from 'vue' |
经过所有这些更改,我们在指南开头的组件和指令将被改写为如下内容:
1 | const app = createApp(MyApp) |
在应用之间共享配置
在应用之间共享配置 (如组件或指令) 的一种方法是创建工厂功能,如下所示:
1 | import { createApp } from 'vue' |
现在,Foo 和 Bar 实例及其后代中都可以使用 focus
指令。
Webpack Treeshacking
在 Vue 3 中,全局和内部 API 都经过了重构,并考虑到了 tree-shaking 的支持。因此,全局 API 现在只能作为 ES 模块构建的命名导出进行访问。例如,我们之前的片段现在应该如下所示:
1 | import { nextTick } from 'vue' |
1 | import { shallowMount } from '@vue/test-utils' |
直接调用 Vue.nextTick()
将导致臭名昭著的 undefined is not a function
错误。
通过这一更改,如果模块绑定器支持 tree-shaking,则 Vue 应用程序中未使用的全局 api 将从最终捆绑包中消除,从而获得最佳的文件大小。