Chapter 02 - Vue 3 Composition API: Simplify Code, Boost Performance, and Supercharge Your Apps

Vue.js Composition API revolutionizes frontend development. It improves code organization, reusability, and maintenance. Key features include ref, reactive, and computed functions, enabling more flexible and efficient component logic structuring.

Chapter 02 - Vue 3 Composition API: Simplify Code, Boost Performance, and Supercharge Your Apps

Vue.js has been a game-changer in the world of frontend development, and with the introduction of the Composition API, it’s taking things to a whole new level. If you’ve been using Vue for a while, you’re probably familiar with the Options API. Well, get ready to have your mind blown because the Composition API is here to shake things up!

So, what’s the big deal about the Composition API? Well, it’s all about making your code more organized, reusable, and easier to maintain. It’s like Marie Kondo for your Vue components – it helps you tidy up and spark joy in your codebase.

Let’s dive into the nitty-gritty of the Composition API and see how it compares to the Options API we know and love. First things first, we need to import the necessary functions from Vue. The main players here are ref, reactive, and computed. These bad boys are going to be your new best friends.

import { ref, reactive, computed } from 'vue'

Now, instead of using the familiar data, methods, and computed properties in our component options, we’re going to use a setup function. This setup function is where all the magic happens. It’s like the backstage area of your component, where you set everything up before the show begins.

Let’s start with a simple example. Say we want to create a counter component. In the Options API, we might do something like this:

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}

Now, let’s see how we can achieve the same thing with the Composition API:

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    
    function increment() {
      count.value++
    }
    
    return {
      count,
      increment
    }
  }
}

See what we did there? We used the ref function to create a reactive reference to our count. The cool thing about ref is that it works with any type of value, not just objects. When we want to access or modify the value, we use .value.

But wait, there’s more! The Composition API really shines when it comes to organizing and reusing logic. Let’s say we want to create a reusable piece of logic for our counter. We can extract it into its own function:

function useCounter(initialCount = 0) {
  const count = ref(initialCount)
  
  function increment() {
    count.value++
  }
  
  return {
    count,
    increment
  }
}

Now we can use this in any component that needs a counter:

import { useCounter } from './useCounter'

export default {
  setup() {
    const { count, increment } = useCounter()
    
    return {
      count,
      increment
    }
  }
}

Pretty neat, huh? This is what we call a “composable” – a function that encapsulates and reuses stateful logic. It’s like having superpowers for your code reusability!

Now, let’s talk about reactive. While ref is great for primitive values, reactive is perfect for objects. It’s like creating a reactive data object in the Options API, but with more flexibility:

import { reactive } from 'vue'

export default {
  setup() {
    const state = reactive({
      firstName: 'John',
      lastName: 'Doe'
    })
    
    function updateName(newFirstName, newLastName) {
      state.firstName = newFirstName
      state.lastName = newLastName
    }
    
    return {
      state,
      updateName
    }
  }
}

One thing to keep in mind is that when you’re using reactive, you don’t need to use .value to access or modify properties. It’s all reactive, all the time!

Now, let’s talk about computed properties. In the Composition API, we use the computed function to create computed values. It’s like having a little Einstein in your code, always ready to do some calculations:

import { reactive, computed } from 'vue'

export default {
  setup() {
    const state = reactive({
      firstName: 'John',
      lastName: 'Doe'
    })
    
    const fullName = computed(() => `${state.firstName} ${state.lastName}`)
    
    return {
      state,
      fullName
    }
  }
}

Cool, right? The computed function returns a read-only reactive reference. It’s like having a super-smart variable that always knows the right answer.

Now, you might be wondering, “What about lifecycle hooks?” Don’t worry, the Composition API has got you covered. Instead of defining lifecycle methods in the component options, we can import and use them directly in the setup function:

import { onMounted, onUnmounted } from 'vue'

export default {
  setup() {
    onMounted(() => {
      console.log('Component is mounted!')
    })
    
    onUnmounted(() => {
      console.log('Component is unmounted!')
    })
  }
}

It’s like having little hooks that you can hang your code on at different stages of your component’s life. Pretty handy!

Now, let’s put it all together in a more complex example. Say we’re building a todo list app. We’ll use the Composition API to create a reusable todo list logic:

import { ref, computed } from 'vue'

export function useTodoList() {
  const todos = ref([])
  const newTodo = ref('')

  const addTodo = () => {
    if (newTodo.value.trim()) {
      todos.value.push({
        id: Date.now(),
        text: newTodo.value,
        completed: false
      })
      newTodo.value = ''
    }
  }

  const removeTodo = (id) => {
    todos.value = todos.value.filter(todo => todo.id !== id)
  }

  const toggleTodo = (id) => {
    const todo = todos.value.find(todo => todo.id === id)
    if (todo) {
      todo.completed = !todo.completed
    }
  }

  const completedTodos = computed(() => {
    return todos.value.filter(todo => todo.completed)
  })

  const incompleteTodos = computed(() => {
    return todos.value.filter(todo => !todo.completed)
  })

  return {
    todos,
    newTodo,
    addTodo,
    removeTodo,
    toggleTodo,
    completedTodos,
    incompleteTodos
  }
}

And here’s how we can use this in a component:

import { useTodoList } from './useTodoList'

export default {
  setup() {
    const {
      todos,
      newTodo,
      addTodo,
      removeTodo,
      toggleTodo,
      completedTodos,
      incompleteTodos
    } = useTodoList()

    return {
      todos,
      newTodo,
      addTodo,
      removeTodo,
      toggleTodo,
      completedTodos,
      incompleteTodos
    }
  }
}

And here’s the template to go along with it:

<template>
  <div>
    <input v-model="newTodo" @keyup.enter="addTodo" placeholder="Add a new todo">
    <button @click="addTodo">Add</button>

    <h2>Todo List</h2>
    <ul>
      <li v-for="todo in incompleteTodos" :key="todo.id">
        <input type="checkbox" @change="toggleTodo(todo.id)">
        {{ todo.text }}
        <button @click="removeTodo(todo.id)">Remove</button>
      </li>
    </ul>

    <h2>Completed Todos</h2>
    <ul>
      <li v-for="todo in completedTodos" :key="todo.id">
        <input type="checkbox" checked @change="toggleTodo(todo.id)">
        <s>{{ todo.text }}</s>
        <button @click="removeTodo(todo.id)">Remove</button>
      </li>
    </ul>
  </div>
</template>

Isn’t that awesome? We’ve created a fully functional todo list app using the Composition API. The logic is neatly organized in a composable function, making it easy to reuse in other components if needed.

Now, you might be thinking, “This is all well and good, but do I have to completely abandon the Options API?” Not at all! Vue 3 is all about flexibility. You can mix and match the Composition API with the Options API as you see fit. In fact, you can even use them together in the same component:

export default {
  data() {
    return {
      message: 'Hello from Options API'
    }
  },
  setup() {
    const count = ref(0)
    const increment = () => count.value++

    return {
      count,
      increment
    }
  },
  methods: {
    logMessage() {
      console.log(this.message)
    }
  }
}

In this example, we’re using both the Options API (with data and methods) and the Composition API (in the setup function). It’s like having the best of both worlds!

One of the great things about the Composition API is how it handles reactivity. In the Options API, we often had to use this.$set to ensure reactivity when adding new properties to objects. With the Composition API, that’s a thing of the past. The reactive function creates a deeply reactive object, so you can add new properties without worrying about reactivity issues.

Another cool feature of the Composition API is the ability to use multiple v-models in a single component. This can be super handy when you’re building complex forms. Here’s a quick example:

import { ref } from 'vue'

export default {
  props: {
    firstName: String,
    lastName: String
  },
  setup(props, { emit }) {
    const firstNameInput = ref(props.firstName)
    const lastNameInput = ref(props.lastName)

    return {
      firstNameInput,
      lastNameInput,
      updateFirstName: (value) => emit('update:firstName', value),
      updateLastName: (value) => emit('update:lastName', value)
    }
  }
}

And in the template:

<template>
  <div>
    <input :value="firstNameInput" @input="updateFirstName($event.target.value)">
    <input :value="lastNameInput" @input="updateLastName($event.target.value)">
  </div>
</template>

This component can now be used with multiple v-models:

<NameInput
  v-model:firstName="firstName"
  v-model:lastName="lastName"
/>

Pretty neat, right? The Composition API gives us so much flexibility in how we structure our components and handle reactivity.

Now, you might be wondering about performance. Good news! The Composition API can actually lead to better performance in some cases. Because it allows us to organize our code more efficiently, we can avoid unnecessary re-renders and optimize our components more easily.

One thing to keep in mind when using the Composition API is the importance of proper organization. While it gives us a lot of flexibility, it’s up to us to use that flexibility wisely. A good practice is to group related functionality together and use clear, descriptive names for our composables and variables.

For example, instead of having a giant setup function with all your component logic, you might break it down into smaller, focused composables:

import { useUser } from './useUser'
import { useProducts } from './useProducts'
import { useCart } from './useCart'

export default {
  setup() {
    const { user, login, logout } = useUser()
    const { products, fetchProducts } = useProducts()
    const { cart, addToCart, removeFromCart } = useCart()

    return {
      // user-related
      user,
      login,
      logout,
      // product-related
      products,
      fetchProducts,
      // cart-