Vue 的 2.x 版本有很多的全局 API 和 配置,他们会在全局范围内改变 Vue 的行为。
- “
- 比如常见的全局 API 有:Vue.component / Vue.mixin / Vue.extend / Vue.nextTick;
- 常见的全局配置有:Vue.config.silent / Vue.config.devtools / Vue.config.productionTip
比如(官方例子),如果你想创建一个全局的组件,你会用到 Vue.component:
- Vue.component('trump-sucks', {
- data: () => ({ position: 'America president', }),
- template: `<h1>Trump is the worst ${position}</h1>`;
- });
或者声明一个全局指令:
- Vue.directive('focus', {
- inserted: el => {
- console.log('聚焦!');
- el.focus();
- },
- });
这样确实比较方便,但是会造成一些问题。首先要明确的一点是,Vue 2.x 在设计上并没有 app(应用)的概念。开发者在使用 Vue 2.x 是所谓的 app 不过是一个用 new Vue()创建的 Vue 实例罢了(呵,不过如此)。由同一个 Vue 构造函数创建的 Vue 实例都会共享来自构造函数的全局配置。这将会导致:
在测试过程中,由于全局配置的存在,测试用例很容易就会被“污染”。开发者需要小心翼翼地将全局配置找一个地方存下来,在每次测试结束后将其还原,比如 Vue.config.errorHandler;一些 API 比如 Vue.use 和 Vue.mixin 甚至没有避免其影响的办法。这使得在测试中一旦涉及了插件,整个过程都会变得非常棘手。事实上,为了解决这个问题,vue-test-utils 不得不引入一个特殊的 API:createLocalVue:
- import { createLocalVue, mount } from '@vue/test-utls';
- const localVue = createLocalVue();
- localVue.use(MyPlugin);
- mount(Component, { localVue });
还有一个避免不了的问题是,一旦页面上有多个 Vue 实例时,它们的全局配置就很自然地共享了,但在很多时候开发者并不想要这样的结果
- Vue.mixin({
- mounted: () => {
- console.log('wubba lubba dub dub');
- },
- });
- const rick = new Vue({ el: '#rick' });
- const morty = new Vue({ el: '#morty' });
因此,为了规避这些问题,Vue 3 引入了应用实例的概念。
全局 API: createApp
调用 createApp 会返回一个 应用实例,没错,应用实例这个概念是 Vue 3 中新引入的。
- import { createApp } from 'vue';
- const app = createApp();
应用实例会暴露一个当前全局 API 的子集。在这个重构工作中,Vue 团队秉承的经验法则是:任何会在全局范围内影响 Vue 行为的 API 都会被迁移至应用实例中去。
2.x 的全局 API 3.x 的应用实例 API 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
其他不会在全局影响 Vue 行为的 api 都已改造为具名导出的构建方式(named exports),就像之前尤雨溪尤大在直播里说的那样:为了支持 TreeShaking。
挂载一个应用实例
在使用 createApp(VueInstance) 得到一个应用实例后,这个应用实例就可以用来把整个 Vue 跟实例挂载到页面上了:
- import { createApp } from 'vue';
- import MyApp from './MyApp.vue';
- const app = createApp(MyApp);
- app.mount('#app');
在完成了这些改造之后,开篇我们提到的那些例子将会重写成这样:
- app.component('trump-sucks', {
- data: () => ({ position: 'America president', }),
- template: `<h1>Trump is the worst ${position}</h1>`;
- });
- app.directive('focus', {
- inserted: el => {
- console.log('聚焦!');
- el.focus();
- },
- });
- // 至此,所有在 app 所包含的组件树内创建的 Vue 实例才会共享 trump-sucks 这个组件和 focus 这个指令,而 Vue 构造函数并没有被污染。
多个应用实例的配置共享
上文提到的“不是所有开发者都想要的全局配置共享”,在 Vue 3 中可以通过工厂函数的方式实现:
- import { createApp } from 'vue';
- import CaiXuKun from './CXK.vue';
- import WuYiFan from './WYF.vue';
- const createIdolApp = (IdolInstance) => {
- const idolApp = createApp(IdolInstance);
- idolApp.directive('sing-and-dance', {
- inserted: () => {
- console.log('I am cool!');
- },
- });
- }
- createIdolApp(CaiXuKun).mount('#caixukun');
- createIdolApp(WuYiFan).mount('#wuyifan');
这样就能实现多个应用实例的配置共享了:蔡徐坤和吴亦凡都有了一个叫做“唱跳”的 Vue 自定义指令。