本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-05-31
在 Vue2 中没有“app”的概念,我们定义的应用只是通过 new Vue()
创建的根 Vue
实例。从同一个 Vue
构造函数创建的每个根实例共享相同的全局配置,因此:
在测试期间,全局配置很容易意外地污染其他测试用例。用户需要仔细存储原始全局配置,并在每次测试后恢复 (例如重置 Vue.config.errorHandler
)。有些 API 像 Vue.use
以及 Vue.mixin
甚至连恢复效果的方法都没有,这使得涉及插件的测试特别棘手。实际上,vue-test-utils
必须实现一个特殊的 API createLocalVue 来处理此问题。
全局配置使得在同一页面上的多个“app”之间共享同一个 Vue 副本非常困难,但全局配置不同。
// 这会影响两个根实例
Vue.mixin({
/* ... */
})
const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })
Vue3 引入了一个新的 API createApp
来解决上面的问题。
import { createApp } from 'vue'
const app = createApp(options); // 返回一个应用实例
并使用实例API来代替全局API。
Vue.config => app.config
Vue.config.productionTip => 被移除
Vue.config.ignoredElements => app.config.isCustomElement
Vue.component => app.config
Vue.directive => app.directive
Vue.mixin => app.mixin
Vue.use => app.use
Vue.prototype => app.config.globalPropertie
所有其他未改变的全局API在Vue3中被命名为 exports,见全局API Treeshaking
Vue2 中 Vue.config.ignoredElements
的作用是,使 Vue 忽略在 Vue 之外的自定义元素 (e.g. 使用了 Web Components APIs)。否则,它会假设你忘记注册全局组件或者拼错了组件名称,从而抛出一个关于 Unknown custom element
的警告。
// before
Vue.config.ignoredElements = ['my-el', /^ion-/]
// after
const app = Vue.createApp({})
app.config.isCustomElement = tag => tag.startsWith('ion-')
// 重命名可以更好地传达它的功能,新选项还需要一个比旧的 string/RegExp 方法提供更多灵活性的函数
在 Vue2 中,通常在 Vue.prototype
上增加属性以便所有的组件都可以访问到,在 Vue3中被替换为 app.config.globalProperties
,这些属性将被复制在应用程序中实例化一个组件的一部分。
// before
Vue.prototype.$http = () => {}
// after
const app = Vue.createApp({})
app.config.globalProperties.$http = () => {}
在编写插件时,推荐使用 Provide/Inject 来替代 globalProperties
。
插件开发者通常使用 Vue.use
,但是在 Vue3中此方法将停止工作,所以开发者必须在应用程序实例上显式指定使用此插件:
import VueRouter from 'vue-router';
const app = createApp(MyApp)
// 不再是 Vue.use(VueRouter)
app.use(VueRouter)
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')
与在 2.x 根实例中使用 provide
选项类似,Vue 3 应用实例还可以提供可由应用内的任何组件注入的依赖项。
// 在入口
app.provide('guide', 'Vue 3 Guide')
// 在子组件
export default {
inject: {
book: {
from: 'guide',
default: '',
}
},
template: `<div>{{ book }}</div>`, // book => Vue 3 Guide
}
如果是单文件组件中使用:
<template>
{{ book }}
</template>
<script lang="ts">
import { defineComponent, inject } from 'vue'
export default defineComponent({
name: 'App',
setup() {
const book = inject('guide', 'default value'); // name, defaultValue
return {
book
}
}
})
</script>
父组件提供 provide
的写法:
<template>
<MyMarker />
</template>
<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue
export default {
components: {
MyMarker
},
setup() {
provide('location', 'North Pole'); // name, value
provide('geolocation', {
longitude: 90,
latitude: 135
})
}
}
</script>
需要注意的是在使用 provide
提供一些全局公用插件实例时,使用 inject
选项 去接受的时候会导致 Typescript 无法识别。
// main.ts
const app = createApp(App)
.provide("store", store)
.use(router); // 支持链式调用
// component
export default defineComponent({
name: "Home",
components: {
// ...
},
inject: ["store"], // 使用选项接收
computed: {
email() {
return this.store.state.user.email;
// Warning: Property 'store' does not exist on type 'ComponentPublicInstance ...'
},
},
});
这个时候可以这样来避免上述问题:
// main.ts
const app = createApp(App).use(router);
app.config.globalProperties.store= store;
// shims.d.ts
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
store: any // replace it with the right type
}
}
// component 与上面一样无需改动 就可以直接使用 this.store
在 Vue 3 中,全局和内部 API 都经过了重构,并考虑到了 tree-shaking 的支持。因此,全局 API 现在只能作为 ES 模块构建的命名导出进行访问。
这样,一些全局的API,在你从不使用的时候是不会出现Vue上的,不像Vue2.0+,不管你有没有使用Vue.nextTick
,这一部分的代码都会存在Vue上。
// 在Vue3需要这样引入,便于静态分析的tree-shaking
import { nextTick } from 'vue'
nextTick(() => {
// 一些和DOM有关的东西
})
受影响的全局API有:
同时,除了公共 api,许多内部组件/帮助器现在也被导出为命名导出,只有当编译器的输出是这些特性时,才允许编译器导入这些特性。
<transition>
<div v-show="ok">hello</div>
</transition>
会被编译为
import { h, Transition, withDirectives, vShow } from 'vue'
export function render() {
return h(Transition, [withDirectives(h('div', 'hello'), [[vShow, this.ok]])])
}
这样的好处是,如果你不使用 Transition
组件,那么你的最终打包代码中将不会出现此功能的代码。
在Vue3中,如果你需要使用全局API,必须显示导入
import { nextTick } from 'vue'
const plugin = {
install: app => {
nextTick(() => {
// ...
})
}
}
这样,你的插件打包的时候可能因为模块捆绑包的影响,引入了整个Vue的代码,这并不是你所期望的。防止这种情况发生的一种常见做法是配置模块绑定器以将 Vue 从最终捆绑中排除。
// 在webpack中
module.exports = {
/*...*/
externals: {
vue: 'Vue' // 这将告诉 webpack 将 Vue 模块视为一个外部库,而不是捆绑它。
}
}
// 如果你使用的是rollup 你基本上可以免费获得相同的效果
// 因为默认情况下,Rollup 会将绝对模块 id (在我们的例子中为 'vue') 作为外部依赖项,而不会将它们包含在最终的 bundle 中。
// 但是在绑定期间,它可能会发出一个“将 vue 作为外部依赖” 警告,可使用 external 选项抑制该警告:
// rollup.config.js
export default {
/*...*/
external: ['vue']
}