Vue3 Preliminary Exploration (3) Global API

The global APIs of the Vue2 era are all mounted on the Vue prototype, so any modification to the global API will affect all Vue instances. This may be undesirable, and such modifications are irreversible.

Therefore, in order to cope with this situation, Vue3 will transform the global API and introduce the concept of APP. Each instance is an APP, and the global API acts on the APP.

Vue 2.x has many global APIs and configurations that can change the behavior of Vue globally. For example, to create global components, you can use an API like’Vue.component ':

1
2
3
4
5
6
Vue.component('button-counter', {
data: () => ({
count: 0
}),
template: '<button @click="count++">Clicked {{ count }} times.</button>'
})

Similarly, the declaration method for using global directives is as follows:

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

Although this declaration is convenient, it can also cause some problems.

Technically, Vue 2 has no concept of an “app”, the app we define is just a root Vue instance created with’new Vue () '. Each root instance created from the same Vue constructor function ** shares the same global configuration **, so:

  • During testing, ** global configuration can easily accidentally pollute other test cases. Users need to carefully store the original global configuration ** and restore it after each test (such as resetting’Vue.config.errorHandler ‘). Some APIs ** like’Vue.use’ and’Vue.mixin ‘don’t even have a way to restore the effect **, which makes testing involving plugins particularly tricky. In fact, vue-test-utils must implement a special API’createLocalVue’ to handle this issue:
1
2
3
4
5
6
7
8
9
10
import { createLocalVue, mount } from '@vue/test-utils'

//build the extended'Vue 'constructor function
const localVue = createLocalVue()

//install plugin "globally" on "local" Vue constructor function
localVue.use(MyPlugin)

//Mount options via'localVue '
mount(Component, { localVue })
  • Global configuration makes it very difficult to share the same copy of Vue between multiple “apps” on the same page, but global configuration is different.

    1
    2
    3
    4
    5
    6
    7
    //This affects both root instances
    Vue.mixin({
    /* ... */
    })

    const app1 = new Vue({ el: '#app-1' })
    const app2 = new Vue({ el: '#app-2' })

In order to avoid these problems, in Vue 3 we introduced…

A new global

Calling createApp returns an application instance, which is a new concept in Vue 3.

1
2
3
import { createApp } from 'vue'

const app = createApp({})

The application instance exposes a subset of the current global API. The rule of thumb is that any API that globally changes the behavior of Vue will now be moved to the application instance. Here is a table of the current global API and its corresponding instance API:

2.x Global API3.x Instance API (‘app’)
Vue.configapp.config
Vue.config.productionTipremoved (见下方)
Vue.config.ignoredElementsapp.config.isCustomElement (见下方)
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use (见下方)

All other global APIs that do not change behavior globally are now named exports

Instructions for Plugin Users

Plugin developers usually use’Vue.use ‘. For example, how the official’vue-router’ plugin installs itself in the browser environment:

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

Since the use global API is no longer in use in Vue 3, this method will stop working and calls to Vue.use () will now trigger a warning, so developers must explicitly specify the use of this plugin on the application instance.

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

Mount

After initializing with createApp (/* options */), the app instance app can be used to mount with app.mount (domTarget):

1
2
3
4
5
import { createApp } from 'vue'
import MyApp from './MyApp.vue'

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

With all these changes, the components and directives at the beginning of our guide will be rewritten as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()
})

//Now that all app instances are mounted, along with its component tree, will have the same "button-counter" component, and the "focus" directive does not pollute the global environment
app.mount('#app')

Share configuration between apps

One way to share configuration (such as components or instructions) between applications is to create factory functions as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
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')

The’focus’ directive is now available for both Foo and Bar instances and their descendants.

Webpack

In Vue 3, both global and internal APIs have been refactored with tree-shaking support in mind. As a result, global APIs are now only accessible as named exports built by ES modules. For example, our previous snippet should now look like this:

1
2
3
4
5
import { nextTick } from 'vue'

nextTick(() => {
//something related to DOM
})
1
2
3
4
5
6
7
8
9
10
11
12
13
import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'
import { nextTick } from 'vue'

test('an async feature', async () => {
const wrapper = shallowMount(MyComponent)

Perform some DOM-related tasks

await nextTick()

//run your assertion
})

Calling Vue.nextTick () directly causes the infamous undefined is not a function error.

With this change, if the module binder supports tree-shaking, unused global APIs in Vue applications will be eliminated from the final bundle, resulting in optimal file sizes.