Введение
Экосистема 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.
gmtd
Лучше разделить названия Vue Composition API и Vue Composition API (script setup)