Vue JS Syntax Comparison
Introduction
The Vue JS ecosystem is evolving every year. At the moment, there are several different syntaxes:
There are also projects with the syntax:
Many of us have to work in different projects with different syntax.
Keeping all the spellings in your head is hard enough. And constantly leafing through different documentation – for a long time.
I’m already silent about the beginners who did not have time to study all the documentation options.
Personally, I have several projects with different syntaxes, so I decided to write a cheat sheet for all options in order to always have the necessary templates at hand.
In addition, newcomers to Vue will be able to compare different syntaxes and choose the one they like and better navigate different projects.
Documentation used:
Before studying comparisons, I recommend reading the article about differences script setup.
Defining a Page Component
Vue Options API
<template>
<!-- ... -->
</template>
<script>
export default {
name: "ParentComponent",
};
</script>
Vue composition API
<template>
<!-- ... -->
</template>
<script>
export default {
// ...
}
</script>
Vue composition API
<template>
<!-- ... -->
</template>
<script setup>
// ...
</script>
Vue class API
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({})
export default class ParentComponent extends Vue {
// ...
}
</script>
Vue Class API + vue-property-decorator
The syntax is similar to the Vue Class API.
Registering a Child Component
Vue Options API
<template>
<child-component />
</template>
<script>
import ChildComponent from "@/components/ChildComponent.vue";
export default {
name: "ParentComponent",
components: {
ChildComponent,
},
};
</script>
Vue composition API
<template>
<child-component />
</template>
<script setup>
import ChildComponent "@/components/ChildComponent.vue"
// ...
</script>
Vue composition API
<template>
<child-component />
</template>
<script>
import ChildComponent from "@/components/ChildComponent.vue";
export default {
components: {
ChildComponent,
},
};
</script>
Vue class API
<template>
<child-component />
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import ChildComponent from "@/components/ChildComponent.vue";
@Options({
components: {
ChildComponent,
},
})
export default class ParentComponent extends Vue {}
</script>
Vue Class API + vue-property-decorator
The syntax is similar to the Vue Class API.
Definition of reactive data
Vue Options API
<script>
export default {
data() {
return {
someObject: {}
}
}
}
</script>
Vue composition API
<script>
import { ref } from 'vue'
export default {
setup() {
const someObject = ref({})
return {
someObject
}
}
}
</script>
Vue composition API
<script setup>
import { ref } from 'vue'
const someObject = ref({})
</script>
Vue class API
<script>
import { Options, Vue } from "vue-class-component";
import ChildComponent from "@/components/ChildComponent.vue";
@Options({})
export default class ParentComponent extends Vue {
message = "Hello World!";
}
</script>
Vue Class API + vue-property-decorator
The syntax is similar to the Vue Class API.
Defining a computed property
Vue Options API
<script>
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
fullName: {
get() {
return this.firstName + ' ' + this.lastName
},
set(newValue) {
[this.firstName, this.lastName] = newValue.split(' ')
}
}
}
}
</script>
Vue composition API
<script>
import { reactive, computed } from 'vue'
export default {
setup() {
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
return {
publishedBooksMessage
}
}
}
</script>
<script setup>
import { ref, computed } from 'vue'
export default {
setup() {
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get() {
return firstName.value + ' ' + lastName.value
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
return {
fullName
}
}
}
</script>
Vue composition API
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get() {
return firstName.value + ' ' + lastName.value
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
Vue class API
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({})
export default class ParentComponent extends Vue {
firstName = "John";
lastName = "Doe";
get name() {
return this.firstName + " " + this.lastName;
}
set name(value) {
const splitted = value.split(" ");
this.firstName = splitted[0];
this.lastName = splitted[1] || "";
}
}
</script>
Vue Class API + vue-property-decorator
Syntax is similar to Vue Class API
Defining and calling emit
Vue Options API
<template>
<button @click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
</template>
<script>
export default {
methods: {
myFunction(data) {
this.$emit('emitName', data)
}
}
}
</script>
Vue composition API
<script>
export default {
emits: ['submit']
setup() {
const myFunction = (data) => {
$emit('submit', data)
}
}
}
</script>
<script>
export default {
emits: {
submit(payload) {
// ...
}
}
}
</script>
<script>
export default {
emits: {
// No validation
click: null,
// Validate submit event
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
methods: {
submitForm(email, password) {
this.$emit('submit', { email, password })
}
}
}
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
<script>
export default {
emits: ['inFocus', 'submit'],
setup(props, ctx) {
ctx.emit('submit')
}
}
</script>
Vue composition API
<script setup>
const emit = defineEmits(['inFocus', 'submit'])
function buttonClick() {
emit('submit')
}
</script>
<script setup>
const emit = defineEmits({
submit(payload) {
// return `true` or `false` to indicate
// validation pass / fail
}
})
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input v-model="value" />
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
</script>
Vue class API
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({
emits: ["submit"],
})
export default class ChildComponent extends Vue {
onClick() {
this.$emit("submit");
}
}
</script>
Vue Class API + vue-property-decorator
<script lang="ts">
import { Vue, Component, Emit } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
count = 0
@Emit()
addToCount(n: number) {
this.count += n
}
@Emit('reset')
resetCount() {
this.count = 0
}
@Emit()
returnValue() {
return 10
}
@Emit()
onInputChange(e) {
return e.target.value
}
@Emit()
promise() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(20)
}, 0)
})
}
}
</script>
The code above is equivalent to:
<script>
export default {
data() {
return {
count: 0,
}
},
methods: {
addToCount(n) {
this.count += n
this.$emit('add-to-count', n)
},
resetCount() {
this.count = 0
this.$emit('reset')
},
returnValue() {
this.$emit('return-value', 10)
},
onInputChange(e) {
this.$emit('on-input-change', e.target.value, e)
},
promise() {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve(20)
}, 0)
})
promise.then((value) => {
this.$emit('promise', value)
})
},
},
}
</script>
Definition of lifecycle hooks
Vue Options API
<script>
export default {
mounted() {
// ...
}
}
</script>
Vue composition API
<script>
import { onMounted } from 'vue'
export default {
onMounted() {
// ...
}
}
</script>
Vue composition API
<script setup>
import { onMounted } from 'vue'
const el = ref()
onMounted(() => {
el.value // <div>
})
</script>
Vue class API
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({})
export default class ChildComponent extends Vue {
mounted() {
console.log("mounted");
}
}
</script>
Vue Class API + vue-property-decorator
Syntax is similar to Vue Class API
Defining Methods and Functions
Vue Options API
<script>
export default {
data() {
return {
someObject: {
one: "one",
two: "two",
},
};
},
methods: {
funcOne() {
console.log(this.someObject.one);
},
},
}
</script>
Vue composition API
<script>
export default {
import { ref } from 'vue'
setup() {
const someObject = ref({ one: "one", two: "two" })
const funcOne = () => {
console.log(someObject.value.one)
}
return {
someObject,
funcOne
}
}
}
</script>
Vue composition API
<script setup>
import { ref } from 'vue'
const someObject = ref({ one: "one", two: "two" })
const funcOne = () => {
console.log(someObject.value.one)
}
</script>
Vue class API
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import ChildComponent from "@/components/ChildComponent.vue";
@Options({
components: {
ChildComponent,
},
})
export default class ParentComponent extends Vue {
someObject = { one: "one", two: "two" };
funcOne() {
console.log(this.someObject.one);
}
}
</script>
Vue Class API + vue-property-decorator
Syntax is similar to Vue Class API
Defining the props property
Vue Options API
Defining props without validation
<script>
export default {
props: ['foo'],
}
</script>
props validation:
<script>
export default {
props: {
propA: Number,
propB: [String, Number],
propC: {
type: String,
required: true
},
propD: {
type: Number,
default: 100
},
propE: {
type: Object,
default(rawProps) {
return { message: 'hello' }
}
},
propF: {
validator(value) {
return ['success', 'warning', 'danger'].includes(value)
}
},
propG: {
type: Function,
default() {
return 'Default function'
}
}
}
}
</script>
Vue composition API
Defining props without validation
In order to announce props
with support for full type inference, we can usedefineProps
which are automatically available inside <script setup>
<script setup>
const props = defineProps(['foo'])
</script>
props validation:
<script setup>
const props = defineProps({
propA: Number,
propB: [String, Number],
propC: {
type: String,
required: true
},
propD: {
type: Number,
default: 100
},
propE: {
type: Object,
default(rawProps) {
return { message: 'hello' }
}
},
propF: {
validator(value) {
return ['success', 'warning', 'danger'].includes(value)
}
},
propG: {
type: Function,
default() {
return 'Default function'
}
}
})
</script>
Vue composition API
Defining props without validation
<script>
export default {
props: ['foo']
}
</script>
props validation: similar to Vue Options API syntax.
Vue class API
Defining props without validation
<script lang="ts">
import { Options, Vue } from "vue-class-component";
@Options({
props: {
msg: String,
},
})
export default class ChildComponent extends Vue {
msg!: string;
}
</script>
props validation: similar to other syntaxes
Vue Class API + vue-property-decorator
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@Prop(Number) readonly propA: number | undefined
@Prop({ default: 'default value' }) readonly propB!: string
@Prop([String, Boolean]) readonly propC: string | boolean | undefined
}
</script>
The code above is equivalent to:
<script>
export default {
props: {
propA: {
type: Number,
},
propB: {
default: 'default value',
},
propC: {
type: [String, Boolean],
},
},
}
</script>
Defining the watch property
Vue Options API
Basic usage
<script>
export default {
watch: {
someObject(newValue, oldValue) {
// ...
}
}
}
</script>
Deep Tracking
<script>
export default {
watch: {
someObject: {
handler(newValue, oldValue) {
// ...
},
deep: true
}
}
}
</script>
Mandatory callback
<script>
export default {
watch: {
question: {
handler(newValue, oldValue) {
// ...
},
immediate: true
}
}
}
</script>
Vue composition API
Basic usage
<script setup>
import { watch } from 'vue'
watch(someObject, async (newValue, oldValue) => {
// ...
})
</script>
Deep Tracking
When you call watch()
directly reactive object, it implicitly creates a deep observer – callback
will run for all nested mutations:
This is to be distinguished from a getter which returns a reactive object – in the latter case a callback
will only work if the getter returns a different object:
<script setup>
import { watch } from 'vue'
watch(
() => state.someObject,
() => {
// ...
}
)
</script>
However, you can force the second case to be a deep observer by explicitly using deep
parameter:
<script setup>
import { watch } from 'vue'
watch(
() => state.someObject,
(newValue, oldValue) => {
// ...
},
{ deep: true }
)
</script>
Vue 2.7/3 Composition API
<script setup>
export default {
watch: {
someObject(newValue, oldValue) {
// ...
},
},
setup() {
// ...
}
};
</script>
Vue class API
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import ChildComponent from "@/components/ChildComponent.vue";
@Options({
watch: {
someString(newValue) {
console.log(newValue);
},
},
})
export default class ParentComponent extends Vue {
someString = "old";
mounted() {
this.someString = "new";
}
}
</script>
Vue Class API + vue-property-decorator
<script>
import { Vue, Component, Watch } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@Watch('child')
onChildChanged(val: string, oldVal: string) {}
@Watch('person', { immediate: true, deep: true })
onPersonChanged1(val: Person, oldVal: Person) {}
@Watch('person')
onPersonChanged2(val: Person, oldVal: Person) {}
}
</script>
The code above is equivalent to:
<script>
export default {
watch: {
child: [
{
handler: 'onChildChanged',
immediate: false,
deep: false,
},
],
person: [
{
handler: 'onPersonChanged1',
immediate: true,
deep: true,
},
{
handler: 'onPersonChanged2',
immediate: false,
deep: false,
},
],
},
methods: {
onChildChanged(val, oldVal) {},
onPersonChanged1(val, oldVal) {},
onPersonChanged2(val, oldVal) {},
},
}
</script>
Working with Vuex
Vue Options API
<script>
import { mapGetters, mapState } from "vuex";
export default {
computed: {
...mapGetters(["userId"]),
...mapState({
pageTitle: (state) => state.pageTitle.toUpperCase(),
})
},
methods: {
getUserId() {
return this.$store.state.userId
},
deleteUser(id) {
this.$store.commit("deleteUser", id);
},
}
}
</script>
Vue composition API
<script>
import { mapGetters, mapState, useStore } from "vuex";
export default {
computed: {
...mapGetters(["userId"]),
...mapState({
pageTitle: (state) => state.pageTitle.toUpperCase(),
})
},
setup() {
const store = useStore()
const deleteUser = (id) => {
store.commit("deleteUser", id);
}
const getUserId = () => {
return store.state.userId
}
const deleteUser = (id) => {
store.commit('deleteUser', id)
}
}
}
</script>
Vue composition API
<script>
const store = useStore()
const deleteUser = (id) => {
store.commit("deleteUser", id);
}
const getUserId = () => {
return store.state.userId
}
const deleteUser = (id) => {
store.commit('deleteUser', id)
}
const userId = computed(() => {
return store.getters.userId
})
const count = computed(() => store.getters.count)
</script>
Vue class API
Vuex does not provide types for the this.$store property out of the box.
When used with TypeScript, you must declare a native module extension.
Details.
Vue Class API + vue-property-decorator
Similar to the Vue Class API.