Ivaylo Gerchev All things are difficult before they are easy.

Comparing the Vue 3 Options API and Composition API

7 min read 2227 109

Comparing The Vue 3 Options Api And Composition Api For Component Creation

One crucial aspect of any frontend framework is how it creates components. Interestingly, Vue.js offers not one, but two methods for this process — the Options API and the Composition API. But is this beneficial for developers or not? Let’s find out.

In this article, we’ll compare each option for component creation and discuss how to choose the best method for your needs. We will cover:

At the end of the article, we’ll summarize what we’ve learned in a comparison table for you to reference as you choose between the Composition API or Options API for your project. Let’s jump right in.

Why does Vue.js provide two APIs for component creation?

You might wonder why Vue provides two ways to construct components and whether both methods are necessary. To answer this question, we need to trace back to the origins of Vue.

Vue emerged as a simple and user-friendly library in contrast to Angular’s complexity and React’s disorganization. The goal of Vue was to offer developers a straightforward way to build apps quickly and easily or to inject interactivity into existing projects or websites.

The Options API successfully achieved this goal by providing a clear and straightforward way to describe a component by grouping its code into options. You don’t need extensive thought or code engineering — just place the code in its group of options.

This simplicity is one of the primary reasons developers love Vue so much. However, as Vue gained traction, it matured into a serious framework for building more complex and demanding projects.

This evolution led to the addition of the Composition API in Vue 3, providing a more powerful and flexible approach to creating reusable components. Many people argued against this addition, but when we take a closer look, the reasoning behind this new feature becomes clear.

The Composition API was developed to address the limitations of the Options API, particularly its code organization. In more complex apps, the Options API can lead to spaghetti code and fragmentation. The image below illustrates these differences:

Color Coded Breakdown Of Code Organization Using Composition Api Vs Options Api. Grouping Code Into Options Using Options Api Results In Code Fragmentation While Composition Api Improves Code Integrity

When we use the Options API, we often divide a cohesive unit of code into several pieces and place them into their appropriate options groups. This arrangement works fine for small and simple components this won’t be such a problem.

However, as your app grows and requires more complex and multifunctional components, your code can become challenging to read and maintain. The Composition API solves this problem by allowing you to keep logically related code in one place, resulting in greater code integrity and the ability to write truly reusable code.

As we’ve seen, Vue’s two component-building strategies developed naturally as the language evolved. Don’t let this confuse you!

Throughout the rest of this article, we’ll explore both APIs and their advantages and drawbacks in more detail. Hopefully, this will clarify your understanding and help you to choose the right API for your specific scenarios.



Comparing a Vue.js component built with the Options API vs. the Composition API

If you’re new to Vue, the explanations above may sound a bit abstract. So, let’s get a bit more concrete. First, let’s examine what an Options API structure looks like:

export default {
  data() {
    return {

    }
  },

  computed: {

  },

  methods: {

  }
}

In the example above, data, computed, and methods are all so-called options:

  • data holds all variables
  • computed holds any computed variables
  • methods holds all necessary app functions

There are more options available, but we’re only showing the basic ones here.

Let’s see this in action with a simple To-Do List app example:

<template>
  <h3> My To Do List </h3>
  <div>
    <input v-model="newItemText" v-on:keyup.enter="addNewTodo" />
    <button v-on:click="addNewTodo">Add</button>
    <button v-on:click="removeTodo">Remove</button>
    <button v-on:click="removeAllTodos">Remove All</button>
    <transition-group name="list" tag="ul">
      <li v-for="task in tasks" v-bind:key="task" >{{ task }}</li>
    </transition-group>
  </div>
</template>

<script>
  export default {
    data() { 
      return {
        tasks: ["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"],
        newItemText: ""
      }
    },
    methods: {
      addNewTodo() {
        if (this.newItemText != "") {
          this.tasks.unshift(this.newItemText)
        }
        this.newItemText = ""
      },
      removeTodo() {
        this.tasks.pop()
      },
      removeAllTodos() {
        this.tasks = []
      }
    }
  }
</script>

<style>
  button {
    margin: 5px;
  }

  ul {
    margin: 30px 0 0 0;
    padding: 0;
    text-align: left;
  }

  li {
    font-size: 1.2em;
    list-style: none;
  }

  .list-enter-active {
    animation: add-item 1s;
  }

  .list-leave-active {
    position: absolute;
    animation: add-item 1s reverse;
  }

  .list-move {
    transition: transform 1s;
  }

  @keyframes add-item {
    0% {
      opacity: 0;
      transform: translateX(150px);
    }
    50% {
      opacity: 0.5;
      transform: translateX(-10px) skewX(20deg);
    }
    100% {
      opacity: 1;
      transform: translateX(0px);
    }
  }
</style>

The example above contains two variables and three functions for adding and removing to-do items. All of them are used in the template.

As you can see, the code organization is pretty straightforward — well-organized and easy to understand. This is because our example here is very simple, but this might not be the case for a more complex component.

Note that I won’t explain the CSS code here. It’s included for completeness and to enhance the UX in our example.

For a complex component, using the Composition API would be a better approach. Here is a short demonstration of this API’s structure:

export default {
  setup() {
    // Composition API code
    return {
      // Composition API for use in the template
    }
  }
}

The Composition API is used in the setup() function — which, interestingly, is just another option of the Options API 🙂. This function allows us to use Vue 3’s reactivity to create reactive variables.

Let’s see this in action with an example that mirrors our previous Options API example:

<script>
  import { ref } from 'vue'

  export default {
    setup() {
      const tasks = ref(["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"])
      const newItemText = ref("")

      function addNewTodo() {
        if (newItemText.value != "") {
          tasks.value.unshift(newItemText.value)
        }
        newItemText.value = ""
      }
      function removeTodo() {
        tasks.value.pop()
      }
      function removeAllTodos() {
        tasks.value = []
      }

      return {
        tasks,
        newItemText,
        addNewTodo,
        removeTodo,
        removeAllTodos
      }
    }
  }
</script>

In this example, we use the ref function to declare reactive variables. Then, we create the necessary functions. Finally, we return all the variables and functions that we want to use in the template.

Comparing Composition API and Options API reusability

As I mentioned earlier, one of the main reasons for creating the Composition API was to enhance reusability. Vue achieves this by introducing a new type of components called composables.

Composables are chunks of code that can be used multiple times in one app and shared across different projects. In the Options API, similar functionality is provided by mixins, but they have some drawbacks compared to composables. Let’s see some examples now.

Let’s say we need a counter functionality that we want to reuse in our app’s components. Here is how we can create this with a mixin:

// CounterMixin.js
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
    set(val) {
      this.count = val
    },
    reset() {
      this.count = 0
    }
  }
}

As you can see, a mixin is just an options object. And as we’ll see in the next example, we can inject this object into our component.

Continuing with the to-do list example, let’s change the title to contain the count variable:

<h3> My To Do List ({{ count }}) </h3>

Then swap the code in the script section with the following:

<script>
  import CounterMixin from './mixins/CounterMixin.js'

  export default {
    mixins: [CounterMixin],
    data() { 
      return {
        tasks: ["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"],
        newItemText: ""
    }},
    mounted() {
      this.set(this.tasks.length)
    },
    methods: {
      addNewTodo() {
        if (this.newItemText != "") {
          this.tasks.unshift(this.newItemText)
          this.increment()
        }
        this.newItemText = ""
      },
      removeTodo() {
        this.tasks.pop()
        this.decrement()
      },
      removeAllTodos() {
        this.tasks = []
        this.reset()
      }
    }
  }
</script>

Here, we import the mixin and then register it by using the mixins option. After that, all the properties of the mixins object are merged with the Vue instance object.

Now we can use the variables and functions from the CounterMixin. We use the mounted() hook to set the initial count for the tasks.

At first glance, mixins might seem great. However, they have some significant drawbacks:

  • Mixins can’t accept parameters, so we can’t use dynamic logic. This makes them less flexible and reusable compared to composables
  • The data in mixins aren’t safe from mutation. A consuming component can “silently” change a mixin’s property, which can lead to hard-to-spot bugs
  • With mixins, we can’t definitively trace a component’s data back to its source. This is true both for locally and globally registered mixins
  • When using multiple mixins, name conflicts may occur. As with the “last one wins” rule in CSS, if you have two properties with the same name coming from different mixins, the property from the last mixin wins

You can avoid all of these disadvantages by using composables instead. Here is a counter composable with the exact same functionality:

// useCounter.js
import { ref, readonly } from 'vue'

export default function useCounter() {
  const count = ref(0)
  const increment = () => { count.value += 1 }
  const decrement = () => { count.value -= 1 }
  const set = (val) => { count.value = val }
  const reset = () => { count.value = 0 }

  return {
    count: readonly(count),
    increment,
    decrement,
    set,
    reset
  }
}

Composables are created by using the same principles as in the setup() function.

We create the necessary variables and functions inside the exported composable function and return the parts we want to reuse later. We also use the readonly() function from the Reactivity API to prevent the count variable from getting mutated by a consuming component.

Let’s now see how this composable is applied. Replace the content of the script section with the following:

<script>
  import { ref, onMounted } from 'vue'
  import useCounter from './composables/useCounter.js'

  export default {
    setup() {
      const tasks = ref(["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"])
      const newItemText = ref("")

      const {count, increment, decrement, set, reset} = useCounter()
      onMounted(() => set(tasks.value.length))

      function addNewTodo() {
        if (newItemText.value != "") {
          tasks.value.unshift(newItemText.value)
        }
        newItemText.value = ""
        increment()
      }
      function removeTodo() {
        tasks.value.pop()
        decrement()
      }
      function removeAllTodos() {
        tasks.value = []
        reset()
      }

      return {
        count,
        tasks,
        newItemText,
        addNewTodo,
        removeTodo,
        removeAllTodos
      }
    }
  }
</script>

Here we import the composable and then extract its variables and functions by using destructuring assignment syntax. We use the onMounted() hook to set the initial count for the to-dos.

As you can see here, we can clearly see where the counter variable and functions came from and exactly which ones are available to use.

Compared to mixins, composables offer several benefits:

  • In composables, the data source for each property or method is clear and traceable. We can see where they are imported from and exactly which properties or methods we use
  • When using multiple composables we can easily avoid name conflicts by renaming properties with the same names
  • Composables allow us to use read-only properties, so we can avoid accidental mutation coming from other components and thus keep data safe
  • Composables create a new local state for each component they are used in, but they can also define a global, shared state

The good news is that you can use composables along with the Options API. If you need professionally created and ready-to-use composables, I recommend looking up an awesome collection called VueUse.

Choosing between the Composition API and Options API for your Vue.js project

So far, we’ve seen that both the Composition API and the Options API are excellent tools with their own benefits and drawbacks. So which one should you choose for your project?

I recommend choosing the Composition API if your project is complex or you anticipate it scaling, as well as if you need to create multi-featured and highly reusable Vue components.

On the other hand, the Options API is great if your project is small and simple, and you don’t expect to scale. It’s ideal for single-featured components that don’t require much reusability, as well as if you need to inject some interactivity into an existing project.

Check out this quick comparison table for the Vue Composition API vs. the Vue Options API to help as you weigh your options:

Vue Composition API
Vue Options API
Best for
Building medium to complex, multi-featured components requiring higher reusability
Building small and simple, single-feature components requiring low reusability
Use in
Medium to complex apps with a lot of logic and features
Simple apps or in web sites for progressive enhancement
Learning curve
Steep
Shallow
Reactivity 
Manual, Exposed, Explicit
Automatic, Hidden, Implicit
Reusability approach
Composables
Mixins
Flexibility
More
Less
Code logic
Group by features
Group by options
Bundle size
Smaller
Bigger
Supported in
Vue 2.7 and Vue 3.0+
All Vue versions
TypeScript support
Yes (better)
Yes

Conclusion

In this article, we explored the two ways of building components provided by Vue — the Options API and the Composition API. As we’ve seen, both provide great functionality but for different scenarios.

The key takeaway here is to make the right decision about which one is the best choice for a particular project and then enjoy building it. If you have any further questions, feel free to comment below.

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. LogRocket Dashboard Free Trial Bannerhttps://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - Start monitoring for free.

Ivaylo Gerchev All things are difficult before they are easy.

Leave a Reply