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 use
definePropswhich 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.

Similar Posts

Leave a Reply