Computer scienceFrontendVue.jsVue 3 Composition API

Lifecycle Hooks in Composition API

9 minutes read

During your exploration of Vue.js, you'll uncover the complex mechanisms of its various features. One such critical aspect is lifecycle hooks, particularly within the Composition API. Although you have some understanding of the Composition API, in this topic, we're focusing specifically on lifecycle hooks. We'll explore their function, how they compare to hooks in the Options API, and their practical applications.

Lifecycle hooks in Vue 3 Composition API

In the Vue 3 Composition API, lifecycle hooks are unique methods that automatically launch at different stages of a component's lifecycle. They give you the power to control what happens when a component is created, updated, or destroyed. For example, the onMounted hook activates when the component is mounted to the DOM. Here's how you might use it:

<script setup>
import { onMounted } from 'vue';

onMounted(() => {
  console.log('Component is mounted');
});
</script>

When the page loads and the component is initialized, we will see in the console:

console output

In this example, the onMounted hook logs a message to the console once the component has been inserted into the DOM. But what happens when the component updates or is unmounted? For these situations, you can use the onUpdated and onUnmounted hooks, respectively.

<script setup>
import { onUpdated, onUnmounted } from 'vue';

onUpdated(() => {
  console.log('Component is updated');
});

onUnmounted(() => {
  console.log('Component is unmounted');
});
</script>

Lifecycle hooks in Composition API vs Options API

In the Options API, lifecycle hooks are defined directly within the component's options. In contrast, in the Composition API, they are imported from 'vue' and used within the script setup of a Vue component.

Let's compare how you might use the mounted hook in both APIs:

Options API:

export default {
  mounted() {
    console.log('Component is mounted');
  },
};

Composition API:

<script setup>
import { onMounted } from 'vue';

onMounted(() => {
  console.log('Component is mounted');
});
</script>

In the Options API example, the mounted hook is defined within the component's options and logs a message to the console when the component is mounted. In the Composition API example, we import the onMounted hook and use it within the script setup block to achieve the same functionality.

One significant difference to remember is that the created hook (and beforeCreate), which is available in the Options API, is not available as a lifecycle hook in the Composition API. Instead, any code that you would normally put in the created hook can simply be written directly within the setup script:

<script setup>
console.log('Component is created');
</script>

The code inside the setup script runs when the component is created, similar to the created hook in the Options API.

Understanding these differences will help you transition from the Options API to the Composition API more smoothly, and leverage the unique features of each to write more efficient and organized code.

The following diagram showcases the complete lifecycle of a component along with available Composition API style lifecycle hooks:

As you might notice, the hooks beforeCreate and created have been replaced here by the setup step. Apart from that, the lifecycle hooks in the Composition API work the same way as in the Options API.

Practical example

To fully understand the utility of lifecycle hooks in the Composition API, let's consider a practical example. Suppose we have a component that fetches data from an API. We can use the onMounted hook to fetch the data as soon as the component is mounted:

<template>
  <div v-if="data">
    <h2>{{ data.title }}</h2>
    <p>{{ data.body }}</p>
  </div>
</template>

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

const data = ref(null);

onMounted(async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts/1')
  data.value = await response.json();
});
</script>

In this example, we use the ref function to create a reactive reference to the data. When the component is mounted, the onMounted hook is triggered, fetching the data and assigning it to data.value. We then use Vue's built-in v-if directive to conditionally render the div only if data is not null (we only use the title and body fields of the response).

Thus, when the component is loaded, we will see the corresponding data on the screen:

result in browser

By understanding and leveraging Vue 3's Composition API lifecycle hooks, we can manage component logic and state effectively, leading to more maintainable and readable code.

Limitations and workarounds with lifecycle hooks

While lifecycle hooks are incredibly helpful, they come with some limitations. For example, you cannot use this inside a lifecycle hook in the Composition API. This is because this does not refer to the component instance like it does in the Options API. Instead, you need to use reactive variables created with ref or reactive. Here's how you can work around this:

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

const count = ref(0);

onMounted(() => {
  count.value++;
});
</script>

In the above example, we use ref to create a reactive reference to count. Then, in the onMounted hook, we increment count.value.

Another limitation to keep in mind is the risk of creating infinite loops when using the onUpdated hook. This hook is called after a component's reactive dependencies change and the component re-renders. However, if you modify a reactive property within the onUpdated hook, it can trigger the component to re-render again. This, in turn, calls the onUpdated hook once more, creating a cycle that can potentially continue indefinitely. This is what we refer to as an infinite loop. Here's an illustrative example:

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

const count = ref(0);

onUpdated(() => {
  count.value++; // This will cause an infinite loop
});
</script>

On the other hand, the onBeforeUpdate hook is not prone to infinite looping because it's called before the component is updated. However, at the time it's executed, the DOM element of the component has not yet changed. This is important to remember when deciding which hook to use.

Understanding these limitations and workarounds can help you write stronger code with Vue's Composition API.

Best practices for using lifecycle hooks

When using lifecycle hooks in the Vue 3 Composition API, there are certain best practices that can help you write more efficient, maintainable, and performant code. Let's delve into these practices.

Use Lifecycle Hooks Sparingly. While lifecycle hooks are highly versatile, their overuse can result in complex and challenging-to-understand code. It's better to use them sparingly and only when necessary. For example, if you find that you're using multiple lifecycle hooks to manage a complex operation, it might be a sign that you should refactor your code into smaller, more manageable functions.

// Avoid doing this
onMounted(() => {
  fetchUserData();
  setupWebSocket();
  initializeThirdPartyPlugin();
});

// Do this instead
onMounted(fetchUserData);
onMounted(setupWebSocket);
onMounted(initializeThirdPartyPlugin);

In the second example, each operation is contained within its own function, making the code easier to read and maintain.

Clean Up After Yourself. If you're using lifecycle hooks to add event listeners or subscriptions, remember to remove them in the onUnmounted hook. This prevents memory leaks and ensures your app runs smoothly. Here's an example:

<script setup>
import { onMounted, onUnmounted } from 'vue';

let eventListener;

onMounted(() => {
  eventListener = () => console.log('Window was resized');
  window.addEventListener('resize', eventListener);
});

onUnmounted(() => {
  window.removeEventListener('resize', eventListener);
});
</script>

In this example, an event listener is added when the component mounts and removed when it unmounts. This ensures that the event listener doesn't continue to run after the component is destroyed, preventing potential memory leaks. Similarly, in the onUnmounted hook, you should clear all timeouts and intervals (using clearTimeout and clearInterval).

Avoid Heavy Operations in Lifecycle Hooks. Heavy operations, like computations, can block the main thread and lead to poor performance. Instead, consider offloading these operations to Web Workers or using them in conjunction with Vue's asynchronous components.

By following these best practices, you can ensure that your use of lifecycle hooks leads to code that is efficient, performant, and easy to maintain.

Conclusion

We've delved extensively into lifecycle hooks within Vue 3's Composition API throughout this topic. We started by providing an overview of lifecycle hooks and explaining how they function within the Composition API.

We compared the use of lifecycle hooks in the Composition API to their use in the Options API, highlighting key differences like the absence of created and beforeCreate hooks in the Composition API and the different syntax needed for each.

Despite the diverse syntax, the essence of lifecycle hooks remains the same – to execute specific code at a certain point in a component's lifecycle (when creating, updating, deleting).

We also discussed the limitations of lifecycle hooks, including the inability to use this and the potential for infinite loops with the onUpdated hook, and offered workarounds for these issues.

Finally, we rounded off with best practices for using lifecycle hooks, emphasizing the importance of using hooks sparingly, avoiding heavy operations within hooks, and cleaning up event listeners and subscriptions with the onUnmounted hook.

Now, let's put what we've learned into practice.

How did you like the theory?
Report a typo