Vue 3 has made significant progress and reached a strong level of maturity with Composition API & Script Setup being the recommended and widely adopted syntax for writing our components. This differs from the options API, which was the preferred approach in Vue 2.
Let's explore together all the ingredients of a Vue 3 component using Composition API and Script Setup.
tl;dr 👇
<script setup>
import {
ref,
reactive,
defineAsyncComponent,
computed,
watch,
onMounted,
} from "vue";
import useComposable from "./composables/useComposable.js";
import TheComponent from "./components/TheComponent.vue";
const AsyncComponent = defineAsyncComponent(() =>
import("./components/AsyncComponent.vue")
);
console.log("Equivalent to created hook");
onMounted(() => {
console.log("Mounted hook called");
});
const enabled = ref(true);
const data = reactive({ variable: false });
const props = defineProps({
elements: Array,
counter: {
type: Number,
default: 0,
},
});
const { composableData, composableMethod } = useComposable();
const isEmpty = computed(() => {
return props.counter === 0;
});
watch(props.counter, () => {
console.log("Counter value changed");
});
const emit = defineEmits(["event-name"]);
function emitEvent() {
emit("event-name");
}
function getParam(param) {
return param;
}
</script>
<template>
<div class="wrapper">
<TheComponent />
<AsyncComponent v-if="data.variable" />
<div
class="static-class-name"
:class="{ 'dynamic-class-name': data.variable }"
>
Dynamic attributes example
</div>
<button @click="emitEvent">Emit event</button>
</div>
</template>
<style scoped>
.wrapper {
font-size: 20px;
}
</style>
Script Setup
A Single File Component still consists of 3 parts. The template, the styles and the script. The two former are almost identical between vue 2 and vue 3, with the latter being completely revamped to the so-called script setup syntax. Declararion is simply adding the setup keyword in the script tag.
<script setup>
// Component logic goes here
// --
// Every variable and method
// will be automatically available
// in the template
</script>
By doing so a lot of boilerplate can be removed because every variable and method declared inside the script setup, will be automatically available in the template.
<script setup>
const text = "Hello world!"
function getParam(param) {
return param;
}
</script>
<template>
{{ text }}
{{ getParam(1) }}
</template>
Reactive Data Declaration
A variable declared with the keyword const
, let
or var
is not automatically reactive. To make it reactive we need to declare it using one of the following helpers.
reactive
for complex types (Arrays, Objects, Maps, Sets)ref
for primitives (String, Number, Boolean)
import { ref, reactive } from 'vue'
const enabled = ref(true)
const data = reactive({ variable: false })
Component Declaration
Simply importing a component will make it available in the template. In the case of a lazy-loaded component, the defineAsyncComponent
should be used.
import { defineAsyncComponent } from "vue";
import TheComponent from "./components/TheComponent.vue";
const AsyncComponent = defineAsyncComponent(() =>
import("./components/AsyncComponent.vue")
);
Computed
Computed values work the same but the syntax is quite different. Declaration is done using the computed helper that accepts a callback and returns the reactive variable.
import { computed } from "vue";
const count = 0;
const isNegative = computed(() => {
return count < 0;
});
Watcher
A watcher can be declared in a similar manner, accepting as a parameter a source and a callback. The source can be one of the following:
- A getter function or a computed that returns a value
- A ref
- A reactive object
- An array of any of the above
import { watch, ref } from "vue";
const counter = ref(0);
watch(counter, () => {
console.log("Counter value changed");
});
WatchEffect
WatchEffect, behaves like a watch but only expects a callback. The sources that will trigger the effect are automatically detected.
import { reactive, watchEffect } from "vue"
const state = reactive({
count: 0,
name: 'Leo'
})
watchEffect(() => {
// Runs immediately
// Logs "Count: 0, Name: Leo"
console.log(`Count: ${state.count}, Name: ${state.name}`)
})
state.count++ // logs "Count: 1, Name: Leo"
state.name = 'Cristiano' // logs "Count: 1, Name: Cristiano"
Lifecycle Hooks
A component has multiple lifecycle hooks that we can utilise according to our needs:
[onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount, onErrorCaptured, onRenderTracked, onRenderTriggered, onActivated, onDeactivated]
The usage is as follows:
import { onMounted } from "vue";
console.log("Equivalent to created hook");
onMounted(() => {
console.log("Mounted hook called");
});
Define Props
Props declaration is done with the defineProps script macro. Script macros don’t need to be imported and the variableselements
and counter
in the following example will be automatically available in the template. All the validation options are supported.
defineProps({
elements: Array,
counter: {
type: Number,
default: 0,
},
});
Define Emits
Emits are declared with another script macro. First, we need to declare them with the defineEmits
helper and then use the return value as the emitter.
<script setup>
const emit = defineEmits(["event-name"]);
function emitEvent() {
emit("event-name");
}
</script>
<template>
<button @click="emitEvent">Emit event</button>
</template>
Composables
Composables are simple statefull functions that can be used to share data and logic between components. They replace mixins with a declarative and more easy to understand and test syntax.
A very basic example of a composable is the following:
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// by convention, composable function names start with "use"
export function useMouse() {
// state encapsulated and managed by the composable
const x = ref(0)
const y = ref(0)
// a composable can update its managed state over time.
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// a composable can also hook into its owner component's
// lifecycle to setup and teardown side effects.
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// expose managed state as return value
return { x, y }
}
<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
Additional Resources
- Refactoring a Component from Vue 2 Options API to Vue 3 Composition API
- Vue 3 Script Setup Cheat Sheet
- Ref vs Reactive — Vue 3 Reactive Data Declaration
- The 5̶. 4 ways to Define a Component in Vue 3
- Function Expression Vs Function Declaration inside Script Setup
- 10 Mistakes to Avoid When Starting with Vue 3
- [Video] A Journey from Vue 2 Options API to Vue 3 Composition API


