Components in Vue 3

Dec 9, 2020

Vue 3 has made some slight changes to how Vue components work. The basic syntax for creating Vue components hasn't changed much, but there's a lot of new features for you to take advantage of. Here's an overview of how components have changed in Vue 3.

Components are Scoped to Apps

If you drop Vue 3 into an existing Vue 2 codebase, odds are the first error you'll see is TypeError: Vue is not a constructor. That's because the Vue global is now no longer a class. Instead of using new Vue() to create a new app, you should use Vue.createApp(). And, instead of registering components globally using Vue.component(), you register components on apps using app.component().

For example, below is a component in Vue 2:

Vue.component('hello', {
  data: () => ({
    name: 'World'
  }),
  template: `
    <div>
      <div>
        <input v-model="name"></input>
      </div>
      <h1>Hello, {{name}}</h1>
    </div>
  `
});

// Displays "Hello, World" initially, changes based on input
const app = new Vue({
  template: '<hello></hello>'
});
app.$mount('#content');

Below is how you would rewrite it for Vue 3:

// Create an app using `createApp()` that uses the `hello` component
const app = Vue.createApp({
  // Displays "Hello, World" initially, changes based on input
  template: '<hello></hello>'
});

// Register the `hello` component
app.component('hello', {
  data: () => ({
    name: 'World'
  }),
  template: `
    <div>
      <div>
        <input v-model="name"></input>
      </div>
      <h1>Hello, {{name}}</h1>
    </div>
  `
});

app.mount('#content');

That's it! There are 4 necessary changes:

$emit() Changes

The basics still work: you still define props the same way, and you can still $emit() events from your component. The only difference is that you now need to explicitly define what events your component emits like how you explicitly define a list of props.

Below is an example of how you can use $emit() with Vue 2:

Vue.component('input-name', {
  data: () => ({ name: 'World' }),
  // When you click the "Update" button, Vue will emit an event `update`
  // to the parent, with the current state of 'name'.
  template: `
    <div>
      <input type="text" v-model="name">
      <button v-on:click="$emit('update', name)">
        Update
      </button>
    </div>
  `
});

const app = new Vue({
  data: () => ({ name: 'World' }),
  // To listen to the 'update' event, you create the `input-name`
  // component with a `v-on:update` attribute. `$event` contains
  // the value of the 2nd parameter to `$emit()`.
  template: `
    <div>
      <div>
        <input-name v-on:update="setName($event)"></input-name>
      </div>
      <h1>Hello, {{name}}</h1>
    </div>
  `,
  methods: {
    // Define a method that Vue will call to handle the 'update' event.
    setName: function(v) {
      this.name = v;
    }
  }
});
app.$mount('#content');

Below is how you would change the above example to work with Vue 3. Besides the usual createApp() and app.component() changes, this example also adds a list of events the component emits.

const app = Vue.createApp({
  data: () => ({ name: 'World' }),
  // To listen to the 'update' event, you create the `input-name`
  // component with a `v-on:update` attribute. `$event` contains
  // the value of the 2nd parameter to `$emit()`.
  template: `
    <div>
      <div>
        <input-name v-on:update="setName($event)"></input-name>
      </div>
      <h1>Hello, {{name}}</h1>
    </div>
  `,
  methods: {
    // Define a method that Vue will call to handle the 'update' event.
    setName: function(v) {
      this.name = v;
    }
  }
});

app.component('input-name', {
  data: () => ({ name: 'World' }),
  // New property in Vue 3:
  emits: ['update'],
  // When you click the "Update" button, Vue will emit an event `update`
  // to the parent, with the current state of 'name'.
  template: `
    <div>
      <input type="text" v-model="name">
      <button v-on:click="$emit('update', name)">
        Update
      </button>
    </div>
  `
});

app.mount('#content');

The setup() Hook

The Composition API is one of the most touted improvements in Vue 3. And the Composition API starts with the setup() function, which is similar to the created() hook, but much more powerful. For example, you can use the Vue global's onMounted() function to add a new mounted() hook to your component from the setup() function:

const app = Vue.createApp({
  data: () => ({ to: 'World' }),
  template: '<hello v-bind:to="to"></hello>'
});

// Prints 'Mounted from component!' followed by 'Mounted from setup!'
app.component('hello', {
  props: ['to'],
  template: '<h1>Hello, {{to}}</h1>',
  mounted: function() {
    console.log('Mounted from component!');
  },
  setup: function(props) {
    Vue.onMounted(() => console.log('Mounted from setup!'));
    return {};
  }
});

The most interesting part of the composition API is that it lets you define Vue components without explicitly instantiating a component using a syntax reminiscent of React hooks. For example, you can rewrite the above hello component using just the setup() function:

app.component('hello', {
  props: ['to'],
  setup: function() {
    Vue.onMounted(() => console.log('Mounted!'));
    return (props) => Vue.h('h1', 'Hello, ' + props.to);
  }
});

Not only can you define hooks in setup(), you can also return a render() function and effectively define your template in the setup() function as well as shown above.

The neat part of the Composition API is that you don't have to use it. It's just another tool in the Vue utility belt. For example, we generally don't recommend using JSX-like render() functions because we prefer plain old HTML templates for portability and versatility. But, in certain cases, this pattern can be very useful, like if you're migrating a legacy React app to Vue.


Vue School has some of our favorite Vue video courses. Their Vue.js Master Class walks you through building a real world application, and does a great job of teaching you how to integrate Vue with Firebase. Check it out!


Did you find this tutorial useful? Say thanks by starring our repo on GitHub!

More Vue Tutorials