Как перейти с Vue 2 на Vue 3?
Миграция на Vue 3
Вступление
Как мы знаем, официальный релиз Vue 3 состоялся 18 сентября 2020 года, прошел уже почти год и мне кажется, что пора бы уже переходить на него. Ноооо… тут не всё так просто.
На текущий момент не все плагины адаптированны под Vue 3, поэтому придется искать им замену, выкручиваться и тд. Я пробовал, и знаете, ничего не получилось.
Но к нашему огромному счастью есть плагин, который имеет ту же API, что и Vue 3. Его нам любезно предоставили сами разработчики, чтобы смягчить переход со второй версии на третью. Плагин называется @vue/composition-api. Сегодня речь пойдет о нём.
Что ждать от этой статьи
Важно: Эта статья вводная, с которой я планирую начать цикл статей, посвященных VUE 3. Поэтому сегодня покажу всего лишь несколько простых примеров, которые помогут разыграть аппетит и плавно начать переход на новую версию Vue.
Давайте начнём!
Установка @vue/composition-api
Для начала нам нужно обновить пакеты. Для этого откроем терминал и воспользуемся командой:
vue upgrade
Но учтите, что у вас должна быть установлена Vue CLI ну и, соответственно, Node.js.
После запуска команды у вас должен появится список пакетов, которые требуется обновить. Если пакеты обновлены, появится сообщение ниже:
DONE Seems all plugins are up to date. Good work!
Установим плагин Vue Composition api:
yarn add @vue/composition-api// ornpm i @vue/composition-api
После этого создадим в папке scr файл, в котором будет инициализироваться @vue/composition-api. Назовем его installCompositionApi.js и добавим код:
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'Vue.use(VueCompositionApi)
Этот файл необходимо импортировать в файл main.js, при этом функция импорта должна располагаться самой первой, чтобы скрипты правильно инициализировались.
1 // INIT COMPOSITION API(VUE 3)
2 import '@/installCompositionApi.js'
3
4 import Vue from 'vue'
5 import router from '@/router'
6 ...
7 ...
На этом подключение @vue/composition-api закончили. Библиотека доступна глобально и может использоваться в любом из файлов.
Переписываем Vuex store
Следующим шагом начнем переписывать имеющийся код. Я бы начал со Vuex стора. Давайте так и сделаем. Возьмём один из файлов, например, модуль, который отвечает за список статей.
import { getArticles } from '@/api/articles'
export default {
namespaced: true,
state: () => ({
articles: [],
}),
mutations: {
SET_ARTICLES(state, articles) {
state.articles = articles
},
},
actions: {
async getAllArticles({ commit }) {
try {
const articles = await getArticles()
commit('SET_ARTICLES', articles)
} catch (error) {
commit('SET_ARTICLES', [])
}
},
},
}
Согласитесь, довольно многословно. Приходится писать дополнительную прослойку в виде мутаций, экшенов, чтобы данные записывались асинхронно. Ну да ладно.
Удаляем всё без зазрения совести и добавляем вот такой:
// @/modules/articlesimport { ref } from '@vue/composition-api'
import { getArticles } from '@/api/articles'const articles = ref([])const useArticles = () => {
const getAllArticles = async () => {
articles.value = await getArticles()
}
return { articles, getAllArticles }
}export default useArticles
Количество строк сократилось и это уже хорошо. Давайте разбираться.
В первой строке импортируется метод ref. Он добавляет реактивность для любой переменной.
ref принимает аргумент и возвращает его завернутым в объект со свойством value, которое затем может быть использовано для доступа или изменения значения реактивной переменной.
В коде выше ref был добавлен для переменной articles, теперь она реактивна и имеет дополнительные свойства.
Далее мы видим функцию, которая получает по api все статьи и записывает их в переменную articles.
Но прошу заметить, что записывает она их в реактивное свойство value. Если этого не сделать, то значение не поменяется.
На этом тут всё. Наш обновленный стор работает точно так же и даже стал значительно проще.
Единственное остаётся вопрос, нужен ли теперь Vuex?
Переписываем компонент
После обновления кода в нашем сторе давайте перепишем компонент, который отвечает за отображения статей.
Компонент пока что выглядит вот так:
// @/pages/articles<template>
<div class="articles">
<h1 class="h1">
{{ title }}
</h1> // Search articles
<input v-model="searchQuery" placeholder="Поиск" /> <template>
<ArticlesTable :articles="articles" />
</template>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
import ArticlesTable from '@/components/Articles/Table'
export default {
name: 'Articles',
components: { ArticlesTable },
props: {
title: {
type: String,
default: '',
}
},
computed: {
...mapState('articles', {
articles: 'articles',
}),
},
async created() {
await this.getAllArticles()
},
watch:
searchQuery(query => {
this.getAllArticles()
},
},
mounted() {
document.title = this.title
console.log(this.$route)
},
methods: {
...mapActions('articles', ['getAllArticles']),
},
}
</script>
Здесь мы видим старый, добрый синтаксис Vue 2. Перепишем его на новый и должно получится вот так:
// @/pages/articles<template>
<div class="articles">
<h1 class="h1">
{{ title }}
</h1> // Search articles
<input v-model="searchQuery" placeholder="Поиск" /> <template>
<ArticlesTable :articles="articlesList" />
</template>
</div>
</template>
<script>
import {
ref,
computed,
watch,
onMounted
} from '@vue/composition-api'
import useArticles from '@/modules/articles'
import ArticlesTable from '@/components/Articles/Table'
export default {
name: 'Articles', components: { ArticlesTable }, props: {
title: {
type: String,
default: '',
}
} setup(props, context) { const searchQuery = ref('')
const { articles, getAllArticles } = useArticles() const articlesList = computed(() => articles)
onMounted(() => {
document.title = props.title
console.log(context.root.$route)
}) watch(searchQuery, query => {
this.getAllArticles()
}) getAllArticles()
return {
searchQuery,
articlesList,
}
},
}
</script>
Скажу сразу, в @vue/composition-api и, соответственно, в Vue 3, this теперь не доступен. Вместо него вы должны использовать context. Чуть позже я о нём расскажу.
Давайте разберём новый переписанный компонент.
В начале блока <script> импортируются хуки. Да, теперь их нужно импортировать, чтобы использовать.
Ниже обратите внимание на метод setup, он является точкой входа, в которой и происходит вся магия реактивности. Он имеет два аргумента:
- props: через него получаем пропсы, которые передаются в компонент
- context: он как раз таки и заменяет наше любимый this. Через него можно получить доступ к методу emit, к переменной slot, attrs и тд. Вот полный список.
attrs: (…)
emit: (…)
isServer: (…)
listeners: (…)
parent: (…)
refs: (…)
root: (…)
slots: {}
ssrContext: (…)
Объект this
Глобальный объект this теперь недоступен в компонентах и мы не можем через него обратиться, например, к объекту $route или к какому нибудь другому глобальному объекту. Чтобы это решить, было добавлено свойство root. Теперь только через него нужно обращаться ко всем глобальным объектам, установленным плагинам и тд.
Свойство value
Обратите внимание на то, что в переписанном компоненте, в хуке computed мы возвращаем переменную articles со свойством value. Это нужно делать обязательно, иначе в переменной articlesList будет реактивный объект, что-то вроде этого:
{_isRef: true}
value: (...)
_isRef: true
get value: ƒ value()
set value: ƒ value(newVal)
__proto__: Object
Во Vue 3 все реактивные переменные теперь имеют свойство value.
Но при использовании реактивных переменных в шаблоне не нужно вызывать свойство value, как здесь:
// Неправильно
<h1>{{ title.value }}</h1>
Объект возвращаемый функцией setup(), будет обработан и избавлен от свойств value.
// Правильно
<h1>{{ title }}</h1>
И чтобы в блоке <template> иметь доступ к свойствам из setup, их нужно обязательно возвращать методом return.
На этом я хочу закончить и продолжить более детальное обсуждение тем уже в следующих статьях.
Заключение
Процесс перехода на третью версию с использованием плагина @vue/composition-api оказался очень простым, так как имеет полную совместимость с Vue 2. Синтаксис не сложный и понятный. Я надеюсь, что у вас не возникнет сложностей с его освоением.
Более детально ознакомиться с Vue Composition API можно по этой ссылке.
Спасибо и до скорых встреч!