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>
<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>