← Technology Archive

Historical archive

Vue 3 Migration Notes and API Changes

A detailed set of notes on migrating from Vue 2 to Vue 3, including the Composition API, application instances, tree shaking, v-model, async components, slots, and directives.

These notes summarize major Vue 3 and ecosystem changes for developers migrating from Vue 2.

New Vue 3 capabilities

Composition API

Vue 2’s Options API can scatter the data and methods for one feature across several component options. In a large component, related logic can be difficult to keep together.

Vue 3’s Composition API makes it easier to extract and group the operations associated with a piece of state.

Teleport

Teleport renders content outside the current component hierarchy, making it useful for UI such as modals that should be mounted beside the main application root.

Fragments

Vue 3 components no longer require a single root element.

The emits component option

Declare component events explicitly. The option can also validate events when expressed as an object.

<template>
  <div @click="$emit('click')">
    <h3>Custom event</h3>
  </div>
</template>

<script>
export default {
  emits: ['click'],
}
</script>

Explicit declarations also help distinguish component events from native listeners and avoid accidental double handling.

Custom renderers

Vue 3 exposes APIs for implementing custom rendering logic beyond the normal DOM renderer.

Global APIs move to the application instance

Vue 2 exposed global APIs such as Vue.component(). Every root instance shared the same global configuration, which could pollute other tests and prevented multiple applications on one page from having independent global settings.

Vue 3 introduces explicit application instances:

Vue 2 global API Vue 3 application API
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
Vue.filter Removed

Tree-shakeable global and internal APIs

Many Vue 2 global APIs were static properties on the Vue constructor. Even when an application did not use them, bundlers could not always remove that dead code.

Affected APIs include:

  • Vue.nextTick
  • Vue.observable, replaced by reactive
  • Vue.version
  • Vue.compile, available only in full builds
  • Vue.set, available only in compatibility builds
  • Vue.delete, available only in compatibility builds

Vue 3 uses module imports so bundlers can omit unused APIs.

A unified v-model API

The component model option and the .sync modifier were replaced by argument-based v-model conventions:

<div id="app">
  <h3>{{ data }}</h3>
  <comp v-model="data"></comp>
</div>
app.component('comp', {
  template: `
    <div @click="$emit('update:modelValue', 'new value')">
      I am comp: {{ modelValue }}
    </div>
  `,
  props: ['modelValue'],
})

Render-function changes

The h function is no longer passed automatically and must be imported. The props structure is flatter, and scoped slots are unified with ordinary slots.

Functional components

Vue 3 functional components are plain functions:

  • Their Vue 2 performance advantage is much smaller, so stateful components are generally recommended.
  • A functional component receives props and context.
  • Single-file component templates no longer use the functional attribute.
  • The { functional: true } option was removed.

Async components

Async components must be wrapped explicitly with defineAsyncComponent():

  • The component option became loader.
  • The loader no longer receives resolve and reject.
  • The loader must return a Promise.

data is always a function

The component data option should be a function that returns the component’s reactive state.

Custom-element allowlists

Vue 3 detects custom elements while compiling templates. Configure isCustomElement for tags that Vue should leave alone. With vue-loader, place it in compiler options:

rules: [
  {
    test: /\.vue$/,
    use: 'vue-loader',
    options: {
      compilerOptions: {
        isCustomElement: tag => tag === 'plastic-button',
      },
    },
  },
]

Changes to is and slots

  • The is attribute is limited to the component element in normal templates.
  • In-DOM templates use v-is for the compatibility behavior.
  • $scopedSlots was removed; all slots are available through $slots and exposed as functions.

Attribute coercion

Most application developers do not need to interact directly with the lower-level coercion changes. See the archived Vue 3 migration overview.

Directive lifecycle hooks

Custom directive hooks now align with component lifecycle names:

  • bind → beforeMount
  • inserted → mounted
  • beforeUpdate is new
  • update was removed in favor of updated
  • componentUpdated → updated
  • beforeUnmount is new
  • unbind → unmounted

These notes describe the Vue 3 migration surface near its initial release. Consult the current Vue documentation before applying them to a new project.