Введение
Экосистема Vue JS развивается с каждым годом. На данный момент существует несколько разных синтаксисов:
Options API
Composition API
Class API
Class API + vue-property-decorator (npm)
Так же встречаются проекты с синтаксисом:
Vue 2 + @vue/composition-api (npm). Использовали до выхода Vue 2.7
Многим из нас приходится работать в разных проектах с разным синтаксисом.
Хранить в голове все варианты написания достаточно сложно. А постоянно листать разные документации - долго.
Я уже молчу про новичков, которые не успели изучить все варианты документаций.
Лично у меня несколько проектов с разными синтаксисами, поэтому я решил написать шпаргалку по всем вариантам, чтобы всегда иметь под рукой нужные шаблоны.
В добавок новички в Vue смогут сравнивать разные варианты синтаксиса и выбрать для себя вариант по душе и лучше ориентироваться в разных проектах.
Используемая документация:
Перед изучением сравнений я рекомендую прочитать статью про отличия script setup.
Определение компонента страницы
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
Синтаксис аналогичен Vue Class API.
Регистрация дочернего компонента
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
Синтаксис аналогичен Vue Class API.
Определение реактивных данных
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
Синтаксис аналогичен Vue Class API.
Определение computed свойства
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
Синтаксис аналогичен Vue Class API
Определение и вызов 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>
Код выше эквивалентен:
<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>
Определение 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
Синтаксис аналогичен Vue Class API
Определение методов и функций
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
Синтаксис аналогичен Vue Class API
Определение props свойства
Vue Options API
Определение props без валидации
<script> export default { props: ['foo'], } </script>
Валидация props:
<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
Определение props без валидации
Для того чтобы объявить props с поддержкой вывода полного типа, мы можем использовать
defineProps, которые автоматически доступен внутри <script setup>
<script setup> const props = defineProps(['foo']) </script>
Валидация props:
<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
Определение props без валидации
<script> export default { props: ['foo'] } </script>
Валидация props: аналогично синтаксису Vue Options API.
Vue Class API
Определение props без валидации
<script lang="ts"> import { Options, Vue } from "vue-class-component"; @Options({ props: { msg: String, }, }) export default class ChildComponent extends Vue { msg!: string; } </script>
Валидация props: аналогично другим синтаксисам
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>
Код выше эквивалентен:
<script> export default { props: { propA: { type: Number, }, propB: { default: 'default value', }, propC: { type: [String, Boolean], }, }, } </script>
Определение watch свойства
Vue Options API
Базовое использование
<script> export default { watch: { someObject(newValue, oldValue) { // ... } } } </script>
Глубокое слежение
<script> export default { watch: { someObject: { handler(newValue, oldValue) { // ... }, deep: true } } } </script>
Обязательный обратный вызов
<script> export default { watch: { question: { handler(newValue, oldValue) { // ... }, immediate: true } } } </script>
Vue Composition API
Базовое использование
<script setup> import { watch } from 'vue' watch(someObject, async (newValue, oldValue) => { // ... }) </script>
Глубокое слежение
Когда вы вызываете watch() непосредственно реактивный объект, он неявно создает глубокий наблюдатель — обратный вызов
будет запускаться для всех вложенных мутаций:
Это следует отличать от геттера, который возвращает реактивный объект — в последнем случае обратный вызов
сработает только в том случае, если геттер вернет другой объект:
<script setup> import { watch } from 'vue' watch( () => state.someObject, () => { // ... } ) </script>
Однако вы можете принудительно перевести второй случай в глубокий наблюдатель, явно используя deep параметр:
<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>
Код выше эквивалентен:
<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>
Работа с 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 не предоставляет типы для свойства this.$store из коробки.
При использовании с TypeScript вы должны объявить расширение собственного модуля.
Подробности.
Vue Class API + vue-property-decorator
Аналогично Vue Class API.
