Как создать приложение вокруг Composition API во Vue 3
Валерий Noveo Developer
29К открытий32К показов
Валерий
Noveo VueJS Developer
Vue 3 и Composition API сегодня
Прошло уже больше года, как Vue 3 был выпущен с его главной особенностью: Composition API. Примерно с осени 2021 года синтаксис script setup
стал рекомендуемым способом создания нового проекта на Vue, так что, надеюсь, будет всё больше и больше серьезных приложений, построенных на третьей версии Vue.
Я также создавал с нуля приложения на этом стеке, и большинство информации здесь почерпнуто из этого опыта. Эта статья призвана показать интересные фишки Composition API и то, как структурировать приложение вокруг него. Речь будет идти о дизайне кода и паттернах, поэтому рекомендую повторить, как работает Vue 3, если ещё чувствуете себя в нём неуверенно
Функции Composable и переиспользование кода
Новый Composition API создает много удобных способов переиспользования кода в компонентах. Вспомним, что во Vue 2 логика разделялась по опциям: data, methods, created, и так далее:
С Composition API мы не ограничены этой структурой и можем разделять код по фичам, а не по опциям:
Vue 3.2 ввел новый синтаксис <script setup>
, который является сахаром функции setup()
, просто делая код более кратким. С этого момента я буду использовать этот синтаксис, так как он наиболее актуален.
Теперь, на мой взгляд, важная вещь. Вместо того, чтобы писать фичи внутри script setup
, мы можем разбить их на отдельные файлы. Вот та же самая логика, но с разделением:
Обратите внимание, что featureA.js
и featureB.js
экспортируют типы Ref
и ComputedRef
, поэтому все эти данные являются реактивными!
Этот сниппет может показаться излишним, но:
- Представьте, что компонент состоит из 500+ строк кода, а не из 10. Благодаря разделению логики на файлы
use__.js
код становится более читабельным. - Мы можем свободно переиспользовать функции сomposable внутри
.js
-файлов в нескольких компонентах! Больше нет ограничений на renderless-компоненты со scoped-слотами или конфликов неймспейса в миксинах. Поскольку сomposable-функции используютref
иcomputed
прямо из Vue, этот код будет работать с любым компонентом.vue
в проекте.
Подводный камень 1: хуки жизненного цикла в setup
Если хуки жизненного цикла (onMounted, onUpdated и так далее) можно использовать внутри setup, то их так же можно использовать и внутри нашей сomposable-функции. Можно даже написать что-то вроде такого:
И это будет работать даже внутри vuex! Вопрос только в том, надо ли так делать ?
При такой гибкости использования важно понимать, как и когда регистрируются эти хуки. Посмотрим на приведенный ниже фрагмент: какие onUpdated
будут зарегистрированы?
Вывод: декларируйте хуки жизненного цикла таким образом, чтобы они синхронно выполнялись в инициализации setup. В остальном для Vue неважно, где и в каком контексте эти хуки находятся.
Подводный камень 2: Асинхронные функции в setup
В логике компонента часто необходимо использовать async/await
. Наивным подходом будет попробовать что-то такое:
Однако если запустить этот код, компонент вообще не будет отрендерен. Почему? Потому что промисы не могут обновить состояние. Мы присваиваем промис к переменной data
, но Vue не может реактивно её обновить. К счастью, есть несколько обходных путей:
Решение 1: ref с синтаксисом .then
Для асинхронного стейта можно использовать синтаксис .then
:
- В начале мы создаём реактивный ref, который имеет значение null.
- Вызывается асинхронная функция myAsyncFunction(). Setup всё ещё выполняется синхронно, компонент становится отрисованным.
- Когда промис myAsyncFunction() разрешается, его результат присваивается к реактивному рефу
data
, и после этого его результат рендерится в DOM.
Плюсы: просто работает.
Минусы: синтаксис чувствуется немного старым и может стать громоздким, если чейнить много .then
и .catch
одновременно.
Решение 2: IIFE
Мы можем сохранить синтаксис async/await, если обернём эту логику в асинхронный IIFE:
Плюсы: синтаксис async / await.
Минусы: на мой взгляд, выглядит немного более грязно. Всё ещё нужен дополнительный ref.
Решение 3. Компонент Suspense (экспериментальная фича)
Если обернуть асинхронный компонент в async/await
, как в самом первом примере!
Плюсы: Самый лаконичный вариант.
Минусы: по состоянию на декабрь 2021 это всё ещё экспериментальная фича, она, вероятно, будет меняться.
Компонент
Решение 4. Сторонние библиотеки для таких ситуаций
(см. Следующий раздел)
Плюсы: Больше гибкости. Не надо писать самому, это зависимость в package.json.
Минусы: Это зависимость в package.json.
Библиотека VueUse
Библиотека VueUse тоже опирается на композиционный подход к построению компонентов и дает много helper-функций. Так же, как мы писали useFeatureA и useFeatureB в самом начале, эта библиотека дает уже готовые хелперы, написанные в композиционном стиле. Вот пример использования:
Я крайне рекомендую это библиотеку; на мой взгляд, это must have для каждого нового приложения на Vue 3:
- Эти хелперы могут сэкономить вам много строк кода и вашего времени. Не надо писать все с нуля или копипастить из проекта в проект.
- Не влияет на размер бандла.
- Простой и понятный исходный код. Если возможностей библиотеки будет недостаточно, можно просто расширить функцию и дописать её самому, как надо. Это означает, что вы не сильно рискуете, решив подключать эту библиотеку.
Вот как эта библиотека решает предыдущую проблему асинхронности в setup через функцию useAsyncState
:
Этот метод позволяет выполнять асинхронную функцию прямо внутри setup и вдобавок даёт возможность указать fallback-состояние и состояние загрузки. Сейчас для меня это предпочтительное решение для асинхронности.
Больше информации: документация useAsyncState.
Если вы используете Typescript
Новый синтаксис defineProps и defineEmits
script setup
даёт более краткий метод декларирования пропов и эмитов:
Лично я всегда предпочитаю типизировать через дженерик, так как это убирает лишний импорт PropType и выглядит более выразительно с типами null
и undefined
, чем { required: false }
в синтаксисе Options API.
Заметьте, что импортировать defineProps и defineEmits не нужно. Это специальные макросы, которые использует Vue. Они обрабатываются во время компиляции в «обычный» синтаксис Options API. Скорее всего, мы будем видеть всё больше и больше подобных макросов в будущих релизах.
Типизация функций composable
Так как TS просил типизировать return каждой функции, в прошлом я писал composables подобным образом:
Сейчас мне это кажется ошибкой. Не обязательно типизировать return каждой composable, так как возвращаемый объект практически всегда будет целиком типизирован имплицитно, когда вы пишете composable. Это сохранит много строк кода.
Если EsLint подчеркивает это как ошибку, напишите '@typescript-eslint/explicit-module-boundary-types': 'error' в конфиг (.eslintrc)
Расширение Volar
Volar пришел на смену Vetur в качестве IDE-расширения для VsCode и WebStorm. Теперь он официально рекомендован для использования во Vue 3. Главное, в чём он хорош, — это типизация пропов и эмитов из коробки. Это отлично помогает, если использовать Typescript.
На данный момент я бы всегда использовал Volar в проектах с Vue 3. Для Vue 2 по-прежнему лучше работает Vetur; по моему опыту, для его работы требуется меньше настроек.
Полезная ссылка: как зарегистрировать глобальные компоненты в Volar.
Архитектура приложения и Composition API
Вынесение логики из файла .vue
Ранее были показаны примеры, где вся логика выполнялась внутри script setup
, а были такие, где компоненты использовали функции composable, которые импортировались из других файлов.
Большой архитектурный вопрос заключается в следующем: следует ли нам выносить всю логику из .vue-файла? Есть свои плюсы и минусы.
Какой личный выбор я сделал для себя:
- Использовать гибридный подход в небольших/средних проектах. В обычных ситауциях писать логику внутри setup. Выносить её в отдельные js/ts файлы, когда компонент слишком разрастается или когда становится ясно, что этот код будет переиспользоваться.
- Для больших проектов просто писать все в composable-функции. Использовать setup исключительно для создания неймспейса в template.
Использование сomposables в open source
Краткий обзор, как composables используются в популярных проектах open source:
Интересно, что composables разбиты на виды private и public. Приватные функции предназначены только для внутреннего использования в Quasar, а публичные могут быть вызваны пользователями библиотеки.
- Composables во Vue storefront.
Vue Storefront были одними из самых первых, кто начали использовать композиционный подход, реализовав его ещё во Vue 2 через vue/composition-api. Интересно, что они оставили эти composables в виде фабрик, на основе которых конкретные CMS-имплементации уже могут их реализовывать.
На данный момент, все composables приватные (используются только изнутри). Хотя проект сейчас в ранней стадии разработки, я предполагаю, что эта папка разрастется в будущем.
- Папка hooks в Element plus.
Element plus тоже внутри использует composables. Здесь они, как правило, привязанны к конкретным UI-компонентам.
Ссылки/Что ещё можно прочитать на эту тему (на английском)
29К открытий32К показов