Vue 3: Options API vs the Composition API

Vue 3: Options API vs the Composition API

·

11 min read

Options API has been the go-to method of creating components in Vue. With the introduction of Vue 3, a new method of creating components was introduced called the composition API

In this article we are going to learn about Options API and composition API, their differences and why composition API was introduced along with an example

This article was originally written on the DeadSimpleChat Blog: Vue 3: Options API vs the Composition API

Options API: An Introduction

Options API was the default method of creating components in Vue 2. When we think of a Vue component structure that would include component logic basic options like

  • method

  • data

  • computed

  • watch

We are talking about the Options API. In the Options API the method is for writing functions, data is the state of the component, computed if for derived state and so on

This method has been the core of vue since its inception. A component created with Options API would look something like this

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++;
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2;
    }
  }
}

Composition API

In the composition API developers can group the component code by functionality or logic, instead of the rigid options-based structure previously envisioned by the Options API

This makes it easier to create larger and more complex components than before and also aids in maintainability and reliability.

To achieve reactivity and flexibility the composition API offers functions like

  • ref

  • reactive and

  • computed

let us look how a component is created using the composition API

import { ref, computed } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const doubleCount = computed(() => count.value * 2);

    function increment() {
      count.value++;
    }

    return {
      count,
      doubleCount,
      increment
    }
  }
}

In summary, the Options API distributes the component logic based on Options type and the composition API allows for the grouping of component logic by concern or feature

Creating a Project to better understand Options API vs Composition API

In this section, we are going to create a project with Options API and then introduce the composition API to better understand the differences between these two APIs

We are going to create a task manager for this example and we are going to use Vite to create the project

Step 1: Create a new Vite project

Write the below code in your terminal to create a new Vue project with Vite

npm create vite@latest my-vue-app -- --template vue

now you have created a new project cd into the my-vue-app directory and run the npm install and npm run dev commands

cd my-vue-app
npm install
npm run dev

Now, Open the code in your code editor. I am using VSCode and go to the local url where your app is running

Step 2: Creating the TaskList Component with the help of Options API

In this section, we are going to create a TaskList component using the Options API

in your components folder create a file called TaskList.vue and paste the below code in it

<template>
    <div class="task-list">

      <!-- Task Input Section -->
      <div class="task-input">
        <input v-model="newTask" placeholder="Add a new task" />
        <button @click="addTask">Add</button>
      </div>

      <!-- Incomplete Tasks -->
      <div class="incomplete-tasks">
        <h3>Incomplete Tasks</h3>
        <ul>
          <li v-for="task in incompleteTasks" :key="task.id">
            <span>{{ task.title }}</span>
            <button @click="completeTask(task.id)">Mark Complete</button>
            <button @click="deleteTask(task.id)">Delete</button>
          </li>
        </ul>
      </div>

      <!-- Completed Tasks -->
      <div class="completed-tasks">
        <h3>Completed Tasks</h3>
        <ul>
          <li v-for="task in completedTasks" :key="task.id">
            <span>{{ task.title }}</span>
            <button @click="incompleteTask(task.id)">Mark Incomplete</button>
            <button @click="deleteTask(task.id)">Delete</button>
          </li>
        </ul>
      </div>

    </div>
  </template>

  <script>
  export default {
    data() {
      return {
        tasks: [],
        newTask: ''
      };
    },
    computed: {
      incompleteTasks() {
        return this.tasks.filter(task => !task.completed);
      },
      completedTasks() {
        return this.tasks.filter(task => task.completed);
      }
    },
    methods: {
      addTask() {
        if (this.newTask.trim()) {
          this.tasks.push({
            id: Date.now(),
            title: this.newTask,
            completed: false
          });
          this.newTask = '';
        }
      },
      deleteTask(taskId) {
        this.tasks = this.tasks.filter(task => task.id !== taskId);
      },
      completeTask(taskId) {
        const task = this.tasks.find(t => t.id === taskId);
        if (task) task.completed = true;
      },
      incompleteTask(taskId) {
        const task = this.tasks.find(t => t.id === taskId);
        if (task) task.completed = false;
      }
    }
  }
  </script>

  <style scoped>
  .task-list {
    width: 80%;
    margin: 20px auto;
    box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.1);
    padding: 20px;
    border-radius: 5px;
  }
  .task-input {
    margin-bottom: 20px;
  }
  </style>

Handling data

We are using the Option APIs data function here which acts as the component's state to store information.

data() {
  return {
    tasks: [],      // tasks Array will hold all the tasks
    newTask: ''  // newTask is of type which will hold the new task
  };
}

Handling functionality through methods

In the Options API we can create JS function to add functionality to our components.

These functions can be created inside the methods options. Here are some of the functions that we are creating inside the methods option

addTask : This function is used to create new tasks

addTask() {
  if (this.newTask.trim()) {
    this.tasks.push({
      id: Date.now(),
      title: this.newTask,
      completed: false
    });
    this.newTask = '';  // Reset the input field
  }
}

deleteTask This method is used to delete a task

deleteTask(taskId) {
  this.tasks = this.tasks.filter(task => task.id !== taskId);
}

completeTask This function is used to mark a task as completed

completeTask(taskId) {
  const task = this.tasks.find(t => t.id === taskId);
  if (task) task.completed = true;
}

incompleteTask This function is used to mark a task as incomplete, if first they were marked as complete by mistake

incompleteTask(taskId) {
  const task = this.tasks.find(t => t.id === taskId);
  if (task) task.completed = false;
}

Computed Properties and Watchers

we can use computed properties to filter tasks

incompleteTasks Here we are using computed properties to filter and display only the tasks that have not been completed yet

incompleteTasks() {
  return this.tasks.filter(task => !task.completed);
}

completedTasks : Likewise here we are filtering the tasks that have been completed and only showing those tasks

completedTasks() {
  return this.tasks.filter(task => task.completed);
}

Our basic tasks list does not use watchers but if it did we can use the Option API watch property to declare the watchers there

Now we have created the TaskList, the next thing is to include it in the App.Vue and see it working

Adding the TaskList to App.Vue

open your App.Vue file and paste the following code there

<script setup>
import TaskList from './components/TaskList.vue';
</script>

<template>
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <TaskList />
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

In the app I have some logos so that it looks good.

Let us learn what we are doing in the App.vue file

Importing the TaskList Component

We need to import the tasklist component from the TaskList.Vue file to App.vue file

import TaskList from './components/TaskList.vue';

Then we have the template and the scoped styles then we are having the TaskList component inside the template like

<template>
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <TaskList />
</template>

and we have our app live and working

Rebuilding the TaskList with Composition API

In this section, we will rebuild the composition API so that you can better understand the differences between the options API and the composition API

In the Options API we had options like

  • data

  • methods

  • computed

  • watch

Let's see what we have in the composition API

Handling data in composition API

Here instead of data Object we have the ref and the reactive functions.

If we want to make a single value reactive (string, boolean, or number etc) then we can use ref and if we want to convert the whole object to reactive reference then use the reactive

In other words for simple properties use ref and for complex properties or Objects use reactive function to make properties /data reactive reference

import {ref,reactive} from 'vue';

const tasks = ref([]);
const new Task = ref('');

Handling functionality in composition api

Methods in composition api are plain JS functions. There is no longer a need to describe them inside the methods Object.

const addTask = () => {
  if (newTask.value.trim()) {
    tasks.value.push({
      id: Date.now(),
      title: newTask.value,
      completed: false
    });
    newTask.value = '';
  }
}

Computed properties and watchers in composition api

Computed properties and watchers use the computed and watchfunctions

import { computed, watch } from 'vue';

const incompleteTasks = computed(() => tasks.value.filter(task => !task.completed));
const completedTasks = computed(() => tasks.value.filter(task => task.completed));

watch(tasks, (newVal, oldVal) => {
  console.log('Tasks have changed!');
});

Complete TaskList Component using Composition API

So we have converted the complete TaskList component from Options API to Composition API

<template>
  <div>
    <input v-model="newTask.value" @keyup.enter="addTask" placeholder="Add a new task">
    <ul>
      <li v-for="task in tasks.value" :key="task.id">
        <input type="checkbox" v-model="task.completed">
        {{ task.title }}
      </li>
    </ul>
  </div>
</template>

<script>
import { ref, computed } from 'vue';

export default {
  name: 'TaskList',
  setup() {
    const tasks = ref([]);
    const newTask = ref('');

    const addTask = () => {
      if (newTask.value.trim()) {
        tasks.value.push({
          id: Date.now(),
          title: newTask.value,
          completed: false
        });
        newTask.value = '';
      }
    };

    return {
      tasks,
      newTask,
      addTask
    };
  }
}
</script>

<style scoped>
/*Any styling the you wish to write*/
</style>

Summary for converting TaskList to Composition API

Setup function: The setup function is the entry point where you declare your data, functions and event listeners as composition API provides grouping by functionality

Reactivity use ref or reactive: Make individual properties a reactive ref using ref or if you want to make the whole Object reactive reference use the reactive

functions: All the functions are declared inside the setup function

Template access: Anything that needs to be accessed in the template must be returned from the setup function

Advanced use-cases

State Management: Composition API + Pinia

Pinia is the new default state management library for Vue Js. If you want to use a store the Pinia API are pretty straightforward. You can use useCoolStore() pattern this is similar to Vueex 5

import { defineStore } from 'pinia';

export const useMyStore = defineStore({
  id: 'mystore',
  state: () => ({
    someStateValue: '',
  }),
  actions: {
    setSomeStateValue(value) {
      this.someStateValue = value;
    },
  },
});

In this above code we are using the defineStore function of Pinia to create a new store and we are naming it mystore

then in this store we have a single reactive state property called someStateValue and we are initialing the state property with an empty string

To modify the value of someStateValue we have the function or action setSomeStateValue

Lastly, we are exporting the state useMyStore so that other components can use it

This article is brought to you by DeadSimpleChat, Chat API and SDK for your website and APP

using Pinia with composition api

let us use the useMyStore within a component that is created with composition api

import { useMyStore } from '@/stores/mystore';

export default {
  setup() {
    const store = useMyStore();

    return { someStateValue: store.someStateValue };
  },
};

Here we are importing the useMyStore function which will provide our component access to the already-defined Pinia store

In the component and since we are using the composition api we have the setup function

inside the setup function we have a const named store and we are assigning the useMyStore function to the store const

This grants us access to the store state that is data and actions that is functions

lastly we are exposing the someStateValue to the components template from the store so that we can use it for rendering the component

Routing with Pinia, Vue Router and composition API

Using Vue Router with Pinia is quite easy

import { useRouter, useRoute } from 'vue-router';

export default {
  setup() {
    const router = useRouter();
    const route = useRoute();

    router.push({ name: 'BestRouteEver' });

    return {};
  },
};

In this example we are using the useRouter and the useRouteHooks provided by the Vue Router.

First we initialize the two const namely router and the route

The router gives us access to the router instance and the route gives us insight into the current route's details

All the integration is within the setup function signifying that we are using the composition api in this example instead of the options api

Lastly to navigate we are using the router.push function to take the application to BestRouteEver

Conclusion

In this example, we learned about the Options API as well as the composition API

Thus composition API is a better option to use than the Options API in Vue js. It is basically aims at solving the same problem but has a more logical organization of code that helps the app scale a lot better with the more readable code base

This article is brought to you by DeadSimpleChat, Chat API and SDK for your website and app

Composition API provides a grouping of code based on logical functionality as compared to Options API which provides a more rigid structure

You can use options api in the Vue 3 as well if you are developing small apps and are more comfortable using the options api. It is more welcoming to new developers coming on to the Vue platform

But for large-scale apps or app that has a lot of complexity the composition API is a better choice

Composition API is also quite good and easy to learn once you understand the basic idea behind it

I would suggest that composition API is even easier to learn for a novice programmer as it provides logical or functional grouping of the code at one place which is better than a scattered approach of Options API which also has a rigid structure

Thank you for reading I hope you liked the article