Dev Tips

Building the Same Component in Vue2 vs. Vue3

Matt Maribojoc

Matt Maribojoc · 9 min read

Feb 25, 2020

With the release of Vue 3 coming soon, many people are wondering “What’s different in Vue 2 vs. Vue 3?”

Although we’ve written articles before about the biggest changes coming, we haven’t really taken a deep look at exactly how our code will change. So to show these changes, we’re going to build a simple form component in both Vue 2 and Vue 3.

By the end of this article, you’ll understand the main programming differences between Vue 2 and Vue 3 and be on your way to becoming a better developer.

Alright – let’s go!

Creating our Template

For most components, the code will be very similar, if not identical, in both Vue 2 and Vue 3. However, Vue 3 has support for Fragments, meaning that components can have more than one root node.

This is especially useful when rendering components in a list to remove unnecessary wrapper div elements. However, in this case, we’ll keep a single root node for both versions of our Form component.

Vue 2

vue
<template>
  <div class="form-element">
    <h2>{{ title }}</h2>
    <input type="text" v-model="username" placeholder="Username" />

    <input type="password" v-model="password" placeholder="Password" />

    <button @click="login">Submit</button>
    <p>Values: {{ username + ' ' + password }}</p>
  </div>
</template>
 

The only real difference is how we access our data. In Vue 3, our reactive data is all wrapped in a reactive state variable – so we need to access this state variable to get our values.

Vue 3

vue
<template>
  <div class="form-element">
    <h2>{{ state.title }}</h2>
    <input type="text" v-model="state.username" placeholder="Username" />

    <input type="password" v-model="state.password" placeholder="Password" />

    <button @click="login">Submit</button>
    <p>Values: {{ state.username + ' ' + state.password }}</p>
  </div>
</template>
 

Setting Up Data

This is where the main difference is – the Vue2 Options API vs. the Vue 3 Composition API.

The Options API separates our code into different properties: data, computed properties, methods, etc. Meanwhile, the Composition API allows us to group our code by function rather than the type of property.

For our form component, let’s say we just have two data properties: a username and a password.

The Vue2 code would look like this – we just toss our two values in the data property.

Vue 2

javascript
export default {
  props: {
    title: String,
  },
  data() {
    return {
      username: "",
      password: "",
    };
  },
};

In Vue 3, we have to put in a little more effort by working with a new setup() method where all of our component initialization should be taking place.

Also, to give developers more control over what is reactive, we have direct access to Vue’s reactivity API.

To create reactive data involves three steps:

  • Import reactive from vue

  • Declare our data using the reactive method

  • Have our setup method return the reactive data so our template can access it

In terms of code, it will look a little like this.

Vue 3

javascript
import { reactive } from "vue";

export default {
  props: {
    title: String,
  },
  setup() {
    const state = reactive({
      username: "",
      password: "",
    });

    return { state };
  },
};

Then, in our template, we access them like state.username and state.password

Creating Methods in Vue 2 vs. Vue 3

The Vue2 Options API has a separate section for methods. In it, we can just define all of our methods and organize them in whatever way we want.

Vue 2

javascript
export default {
  props: {
    title: String,
  },
  data() {
    return {
      username: "",
      password: "",
    };
  },
  methods: {
    login() {
      // login method
    },
  },
};

The setup method in the Vue 3 Composition API also handles methods. It works somewhat similarly to declaring data – we have to first declare our method and then return it so that other parts of our component can access it.

Vue 3

javascript
export default {
  props: {
    title: String,
  },
  setup() {
    const state = reactive({
      username: "",
      password: "",
    });

    const login = () => {
      // login method
    };
    return {
      login,
      state,
    };
  },
};

Lifecycle Hooks

In Vue2, we can access the lifecycle hooks directly from our component options. For our example we’ll be waiting for the mounted event.

Vue 2

javascript
export default {
  props: {
    title: String,
  },
  data() {
    return {
      username: "",
      password: "",
    };
  },
  mounted() {
    console.log("component mounted");
  },
  methods: {
    login() {
      // login method
    },
  },
};

Now with the Vue 3 Composition API, almost everything is inside the setup() method. This includes the mounted lifecycle hook.

However, lifecycle hooks are not included by default so we have to import the onMounted method as its called in Vue 3. This looks the same as importing reactive earlier.

Then, inside our setup method, we can use the onMounted method by passing it our function.

Vue 3

javascript
import { reactive, onMounted } from "vue";

export default {
  props: {
    title: String,
  },
  setup() {
    // ..

    onMounted(() => {
      console.log("component mounted");
    });

    // ...
  },
};

Computed Properties

Let’s add a computed property that converts our username to lowercase letters.

To accomplish this in Vue2, we add a computed field to our options object. From here, we can define our property like this…

Vue 2

javascript
export default {
  // ..
  computed: {
    lowerCaseUsername() {
      return this.username.toLowerCase();
    },
  },
};

The design of Vue 3 allows developers to import what they used and not have unnecessary packages in their project. Essentially, they didn’t want developers to have to include things they never used, which was becoming a growing problem in Vue2.

So to use computed properties in Vue 3, we first have to import computed into our component.

Then, similarly to how we created our reactive data earlier, we can make a piece of reactive data a computed value like this:

Vue 3

javascript
import { reactive, onMounted, computed } from "vue";

export default {
  props: {
    title: String,
  },
  setup() {
    const state = reactive({
      username: "",
      password: "",
      lowerCaseUsername: computed(() => state.username.toLowerCase()),
    });

    // ...
  },
};

Accessing Props

Accessing props brings up an important distinction between Vue 2 and Vue 3 – this means something totally different.

In Vue2, this would almost always refer to the component, not a specific property. While this made things easy on the surface, it made type support a pain.

However, we could easily access props – let’s just add a trivial example like printing out our title prop during the mounted hook:

Vue 2

javascript
export default {
  mounted() {
    console.log("title: " + this.title);
  },
};

However in Vue 3, we no longer use this to access props, emit events, and get properties. Instead, the setup() method takes two arguments:

  • props – immutable access to the component’s props

  • context – selected properties that Vue 3 exposes (emit, slots, attrs)

Using the props argument, the above code would look like this.

Vue3

javascript
export default {
  setup(props) {
    // ...
    onMounted(() => {
      console.log("title: " + props.title);
    });
    // ...
  },
};

Emitting Events

Similarly, emitting events in Vue 2 is very straightforward, but Vue 3 gives you more control over how properties/methods are accessed.

Let’s say, in our case, that we want to emit a login event to a parent component when the “Submit” button is pressed.

The Vue 2 code would just have to call this.$emit and pass in our payload object.

Vue2

javascript
export default {
  methods: {
    login() {
      this.$emit("login", {
        username: this.username,
        password: this.password,
      });
    },
  },
};

However, in Vue 3, we now know that this no longer means the same thing, so we have to do it differently.

Luckily, the context object exposes emit that gives us the same thing as this.$emit

All we have to do is add context as the second parameter to our setup method. We’re going to be destructuring the context object to make our code more concise.

Then, we just call emit to send our event. Then, just like before, the emit method takes two arguments:

  • The name of our event

  • A payload object to pass with our event

Vue 3

javascript
export default {
  setup(props, { emit }) {
    // ...

    const login = () => {
      emit("login", {
        username: state.username,
        password: state.password,
      });
    };

    // ...
  },
};

The Final Vue 2 vs. Vue 3 Code!

Great! We’ve made it through the end. As you can see, all of the concepts are the same in Vue 2 and Vue 3, but some of the ways we access properties have changed a little bit.

Overall, I think that Vue 3 will help developers write much more organized code – especially in large codebases. This is mostly because the Composition API allows you to group code together by specific features and even extract out functionality into their own files and import them into components as needed.

After everything here is our code for the form component in Vue 2.

Vue 2

vue
<template>
  <div class="form-element">
    <h2>{{ title }}</h2>
    <input type="text" v-model="username" placeholder="Username" />

    <input type="password" v-model="password" placeholder="Password" />

    <button @click="login">Submit</button>
    <p>Values: {{ username + ' ' + password }}</p>
  </div>
</template>

<script>
  export default {
    props: {
      title: String,
    },
    data() {
      return {
        username: "",
        password: "",
      };
    },
    mounted() {
      console.log("title: " + this.title);
    },
    computed: {
      lowerCaseUsername() {
        return this.username.toLowerCase();
      },
    },
    methods: {
      login() {
        this.$emit("login", {
          username: this.username,
          password: this.password,
        });
      },
    },
  };
</script>
 

And here it is in Vue 3.

Vue 3

vue
<template>
  <div class="form-element">
    <h2>{{ state.title }}</h2>
    <input type="text" v-model="state.username" placeholder="Username" />

    <input type="password" v-model="state.password" placeholder="Password" />

    <button @click="login">Submit</button>
    <p>Values: {{ state.username + ' ' + state.password }}</p>
  </div>
</template>
<script>
  import { reactive, onMounted, computed } from "vue";

  export default {
    props: {
      title: String,
    },
    setup(props, { emit }) {
      const state = reactive({
        username: "",
        password: "",
        lowerCaseUsername: computed(() => state.username.toLowerCase()),
      });

      onMounted(() => {
        console.log("title: " + props.title);
      });

      const login = () => {
        emit("login", {
          username: state.username,
          password: state.password,
        });
      };

      return {
        login,
        state,
      };
    },
  };
</script>
 

I hope this tutorial helped highlight some of the ways Vue code will look different in Vue 3. If you have any other questions, just leave a reply!

Happy coding!


Join the LearnVue Community

Every week we send out exclusive content to thousands of developers on our mailing list. 100% Free.

Latest Posts

Top Tools

Making a Markdown-Based Blog with Vue and Gridsome

Use Vue with Gridsome is one of the easiest ways to create static websites from just Markdown files.

Matt Maribojoc · 12 min Read More
Top Tools

5 VueUse Library Functions That Can Speed Up Development

VueUse is an open-source project that provides Vue developers with a huge collection of essential Composition API utility functions for both Vue 2 and Vue 3.

Matt Maribojoc · 12 min Read More
Dev Tips

Lazy Load Components in Vue with defineAsyncComponent

Using Vue 3’s defineAsyncComponent feature lets us lazy load components - meaning they’re only loaded when they’re needed.

Matt Maribojoc · 7 min Read More
Essentials

The Beginner’s Guide to Vue Template Refs - with Vue 3 Updates

Vue Template Refs give our Javascript code a reference to easily access the template.

Matt Maribojoc · 5 min Read More