Easy Tutorial
❮ Vue3 Tutorial Vue3 Custom Directive ❯

Vue3 Composition API

The Vue3 Composition API is primarily used to enhance code logic reusability in large components.

Traditional components, as business complexity increases, tend to accumulate more code, making the overall logic difficult to read and understand.

In Vue3, the Composition API is used within the setup function.

Inside setup, we can group code by logical concerns, extract logical fragments, and share code with other components. Thus, the Composition API allows us to write more organized code.

1. Traditional Components

2. Composition API


Setup Component

The setup() function executes before the component's created() lifecycle hook.

The setup() function accepts two parameters: props and context.

The first parameter, props, is reactive and will be updated when new props are passed.

The second parameter, context, is a regular JavaScript object that exposes other values that might be useful within setup.

Note: Avoid using this in setup as it will not reference the component instance. setup is called before data property, computed property, or methods are resolved, so they cannot be accessed within setup.

The following example uses the Composition API to define a counter:

Example (src/App.vue)

<template>
    <div>
        <p>Counter Example: {{ count }}</p>
        &lt;input @click="myFn" type="button" value="Click me to add 1">
    </div>
</template>

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

export default {
    setup() {
        // Define a variable with an initial value of 0, use the ref method for reactivity
        let count = ref(0);

        // Define the click event myFn
        function myFn() {
            console.log(count);
            count.value += 1;
        }

        // Use the onMounted hook to log a message when the component is mounted
        onMounted(() => console.log('component mounted!'));

        // Expose the defined variables or methods for use in the template
        return { count, myFn } // Returned functions behave the same as methods
    }
}
</script>

In Vue 3.0, we can make any reactive variable work anywhere using the new ref function, as shown below:

import { ref } from 'vue'

let count = ref(0);

The ref() function creates a reactive data object based on the given value, returning an object with a single .value property.

Within the setup() function, reactive data created by ref() returns an object, so it needs to be accessed with .value.

Example

import { ref } from 'vue'

const counter = ref(0)

console.log(counter) // { value: 0 }
console.log(counter.value) // 0

counter.value++
console.log(counter.value) // 1

Vue Composition API Lifecycle Hooks

In Vue2, we implemented lifecycle hooks as follows:

Example

export default {
  beforeMount() {
    console.log('V2 beforeMount!')
  },
  mounted() {
    console.log('V2 mounted!')
  }
};

In Vue3 Composition API, lifecycle hooks can be implemented within the setup() function using functions prefixed with on:

Example

import { onBeforeMount, onMounted } from 'vue';
export default {
  setup() {
    onBeforeMount(() => {
      console.log('V3 beforeMount!');
    })
    onMounted(() => {
      console.log('V3 mounted!');
    })
  }
};

The following table maps the Options API to the Composition API, including how to call lifecycle hooks inside setup():

Vue2 Options-based API Vue Composition API
beforeCreate setup()
created setup()
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
errorCaptured onErrorCaptured

Since setup runs around the beforeCreate and created lifecycle hooks, there is no need to explicitly define them. In other words, any code written in these hooks should be directly written in the setup function.

These functions accept a callback function that will be executed when the hook is called by the component:

Example

setup() {
...
    // Log a message using the onMounted hook when the component is mounted
    onMounted(() => console.log('component mounted!'));
...
}

Template Refs

When using the Composition API, the concepts of reactive references and template references are unified.

To obtain a reference to an element or component instance within a template, we can declare a ref as usual and return it from setup():

Example

<template> 
  <div ref="root">This is a root element</div>
</template>

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

  export default {
    setup() {
      const root = ref(null)

      onMounted(() => {
        // The DOM element will be assigned to the ref after initial render
        console.log(root.value) // <div>This is a root element</div>
      })

      return {
        root
      }
    }
  }
</script>

In the above example, we expose root in the rendering context and bind it to the div with ref="root".

Template refs behave like any other ref: they are reactive and can be passed to (or returned from) composite functions.

Usage in v-for

The Composition API template refs do not have special handling when used inside v-for. Instead, use function refs for custom handling:

Example

<template>
  <div v-for="(item, i) in list" :ref="el => { if (el) divs[i] = el }">
    {{ item }}
  </div>
</template>

<script>
  import { ref, reactive, onBeforeUpdate } from 'vue'

  export default {
    setup() {
      const list = reactive([1, 2, 3])
      const divs = ref([])

      // Ensure refs are reset before each update
      onBeforeUpdate(() => {
        divs.value = []
      })

      return {
        list,
        divs
      }
    }
  }
</script>

Watching Template Refs

Watching template refs can replace the lifecycle hooks demonstrated earlier.

However, a key difference from lifecycle hooks is that watch() and watchEffect() run their side effects before the DOM is mounted or updated, so the template ref will not yet be updated when the watcher runs.

Example

<template>
  <div ref="root">This is a root element</div>
</template>

<script>
  import { ref, watchEffect } from 'vue'

  export default {
    setup() {
      const root = ref(null)

      watchEffect(() => {
        // This side effect runs before the DOM is updated, so the template ref does not yet reference the element
        console.log(root.value) // => null
      })

      return {
        root
      }
    }
  }
</script>

Therefore, watchers using template refs should be defined with the flush: 'post' option, which will run the side effect after the DOM is updated, ensuring the template ref is in sync with the DOM and references the correct element.

Example

<template>
  <div ref="root">This is a root element</div>
</template>

<script>
  import { ref, watchEffect } from 'vue'

  export default {
    setup() {
      const root = ref(null)

      watchEffect(() => {
        console.log(root.value) // => <div>This is a root element</div>
      }, 
      {
        flush: 'post'
      })

      return {
        root
      }
    }
  }
</script>
❮ Vue3 Tutorial Vue3 Custom Directive ❯