API Composition Vue 3 и разделение проблем
Перевод: Thomas Ferro — Vue 3’s Composition API and the segregation of concerns
Я всегда думаю, что лучше всего рассматривать вещи в какм либо контексте. Вот почему я написал первую статью о моем взгляде на состояние Vue еще до версии 3.0, которая в настоящее время доступна как релиз-кандидат.
Однако главная тема этой статьи — одна из новых функций Vue 3: Composition API. Это то, что я жду больше всего, и, наконец, пора это обсудить!
Блестящие новые игрушки
Эта статья должна быть самой короткой из моей серии, поскольку эту тему уже много раз обсуждали люди более интересные и умные, чем я.
Composition API было создано для решения двух распространенных проблем, которые возникают, когда ваше приложение Vue начинает усложняться и разрастаться.
Организация кода
Приходилось ли вам когда-нибудь поддерживать действительно большие компоненты со сложной логикой, подразумевающей большие функции data, computed, methods и т. д.? При попытке прочитать эти типы компонентов основная проблема состоит в том, чтобы отслеживать, все что в них делается и как они взаимодействуют друг с другом. С текущим API options вам нужно перемещаться вперед и назад внутри экземпляра Vue, что вызывает большую когнитивную нагрузку.
Vue 3 пытается решить эту проблему, добавляя новый метод к экземпляру Vue, setup. Этот метод можно рассматривать как точку входа в компонент, который вызывается перед обработчиком beforeCreated и получает props в качестве аргумента. Возвращаемое значение — это объект, содержащий всю доступную информацию для использования в шаблоне.
Метод setup ‘nj место, где вы будете писать всю логику вашего компонента, независимо от того, говорим ли мы о data, computed, watcher и т. д.
В этой головоломке все еще есть недостающий элемент: как нам записать data, computed, watcher, methods и многое другое в этом новом методе setup?
Так же Vue 3 предоставляет новый инструмент для создания этих и других реактивных данных: Reactivity API.
Возможно, сейчас это не актуально, но вот небольшой пример того, как создать реактивные данные с помощью Reactivity API:
import { ref } from 'vue'; const count = ref(0); // Accessing ref's value in JS console.log('Count:', count.value) // Modifying the value count.value += 1
Как видите, вам нужно явно получить доступ к значению ref при манипулировании им в JS. Это может немного беспокоит, но вам не придется делать это в шаблоне и вы можете напрямую обращаться к значению, как мы увидим позже.
Пожалуйста, взгляните на справочник Reactivity API для получения дополнительной информации о том, что в нем содержится.
Хорошо, но как эти два API сочетаются друг с другом? Давайте посмотрим на это на примере. Сначала мы напишем компонент с помощью Options API, а затем сделаем это а-ля Vue 3.
Допустим, у нас есть компонент, управляющий загрузкой и отображением сообщений в блоге, минималистичная его версия может быть такой:
export default { name: 'blog-posts', data() { return { posts: [], loadingStatus: '', error: '', }; }, computed: { blogPostsLoading() { return this.loadingStatus === 'loading'; }, blogPostsLoadingError() { return this.loadingStatus === 'error'; }, }, methods: { loadBlogPosts() { this.loadingStatus = 'loading'; fetch(process.env.VUE_APP_POSTS_URL) .then((response) => { if (!response.ok) { throw new Error(response.status); } return reponse.json(); }) .then((posts) => { this.posts = posts; this.loadingStatus = 'loaded'; }) .catch((error) => { this.error = error; this.loadingStatus = 'error'; }); }, }, created() { this.loadBlogPosts(); }, }
Используя новые предоставленные инструменты, мы можем поместить всю логику в setup:
import { ref, computed } from 'vue'; export default { name: 'blog-posts', setup() { const loadingStatus = ref(''); const error = ref(''); const posts = ref([]); const blogPostsLoading = computed(() => { return loadingStatus.value === 'loading'; }); const blogPostsLoadingError = computed(() => { return loadingStatus.value === 'error'; }); const loadBlogPosts = () => { loadingStatus.value = 'loading'; fetch(process.env.VUE_APP_POSTS_URL) .then((response) => { if (!response.ok) { throw new Error(response.status); } return reponse.json(); }) .then((fetchedPosts) => { posts.value = fetchedPosts; loadingStatus.value = 'loaded'; }) .catch((apiError) => { error.value = apiError; loadingStatus.value = 'error'; }); }; // Return every information to be use by the template return { loadingStatus, // You can rename those information if needed loadingError: error, loadBlogPosts, blogPostsLoading, blogPostsLoadingError, }; }, };
Это может показаться не очень полезным в компоненте с небольшим количеством логики, но это уже помогает разработчикам отслеживать различные части без прокрутки между параметрами экземпляра Vue. Позже в этой и следующих статьях мы увидим, как извлечь из этого максимальную пользу.
Мы также можем извлечь логику, создав отдельный модуль (posts.js), управляющий данными и предоставляющий полезную информацию:
import { ref, computed } from 'vue'; export const useBlogPosts = () => { const loadingStatus = ref(''); const error = ref(''); const posts = ref([]); const blogPostsLoading = computed(() => { return loadingStatus.value === 'loading'; }); const blogPostsLoadingError = computed(() => { return loadingStatus.value === 'error'; }); const loadBlogPosts = () => { loadingStatus.value = 'loading'; fetch(process.env.VUE_APP_POSTS_URL) .then((response) => { if (!response.ok) { throw new Error(response.status); } return reponse.json(); }) .then((fetchedPosts) => { posts.value = fetchedPosts; loadingStatus.value = 'loaded'; }) .catch((apiError) => { error.value = apiError; loadingStatus.value = 'error'; }); }; // Return every information to be use by the consumer (here, the template) return { loadingStatus, // You can rename those information if needed loadingError: error, loadBlogPosts, blogPostsLoading, blogPostsLoadingError, }; }
Тогда наш компонент будет управлять шаблоном только на основе данных, предоставленных модулем. Полное разделение проблем:
import { useBlogPosts } from './posts.js'; export default { name: 'blog-posts', setup() { const blogPostsInformation = useBlogPosts(); return { loadingStatus: blogPostsInformation.loadingStatus, loadingError: blogPostsInformation.loadingError, loadBlogPosts: blogPostsInformation.loadBlogPosts, blogPostsLoading: blogPostsInformation.blogPostsLoading, blogPostsLoadingError: blogPostsInformation.blogPostsLoadingError, }; }, };
Опять же, это помогает прояснить ваш код и отделить намерение от реализации, что всегда приятно.
Возможно, вы уже думали об этом, но этот способ создания модулей может помочь нам повторно использовать логику!
Повторное использование логики
У нас уже есть некоторые инструменты, которые помогают нам создавать логику, которая будет использоваться многими компонентами. Например, миксины позволяют писать любые параметры экземпляра Vue для внедрения в один или несколько компонентов.
Но у этого подхода есть одна обратная сторона — ему не хватает ясности.
Вы никогда точно не узнаете, какой точно именно миксин (какой его вариант) импортирован, если не найдете их все. Это может легко стать кошмаром для разработчиков, пытающихся понять, как работают компоненты, из-за необходимости перемещаться по экземпляру Vue и глобально и локально внедренным миксинам. Более того, варианты миксинов могут конфликтовать друг с другом, что легко приводит к беспорядку.
С Composition API любой компонент может выбрать то, что ему нужно, из разных модулей. В методе setup четко указано, что разработчики могут видеть, что было взято и откуда, и даже переименовывать переменную, если это помогает лучше понять цель.
Я думаю, что ясность — это самая важная проблема при написании приложений, которые нужно активно поддерживать в течение многих лет. Composition API дает нам инструмент, позволяющий сделать это элегантным и практичным способом.
Подождите, есть еще что то?
Две основные цели кажутся мне вполне достигнутыми, но Composition API не следует сводить к этим двум проблемам.
Это также улучшает тестируемость наших приложений, позвольте мне объяснить, что это.
До Vue 3 нам приходилось создавать экземпляр компонента, при необходимости вводить фиктивные зависимости и только после этого начинать разбираться с тестами. Такой способ тестирования может привести к созданию наборов тестов, прочно связанных с реальной реализацией. Такие тесты быстро стареют и могут принести больше вреда, чем пользы.
Теперь мы можем создавать ES-модули, инкапсулирующие требуемую логику и экспортирующие данные и методы, которые будут использоваться. Эти модули будут написаны на почти чистом Javascript, поскольку они по-прежнему будут использовать API Vue, но не в контексте компонента.
Наши тесты могут просто потреблять экспортированную информацию, как и наши компоненты!
Искусство обучения
Возможно, вы заметили, что по тому факту, что я пишу об этом целую серию статей, я очень взволнован этим новым API. Он убирает зуд, который у меня был в течение долгого времени, пытаясь применить принципы чистого кода в моих интерфейсных приложениях. Я думаю, что при правильном использовании это поможет нам значительно повысить качество наших компонентов и приложений.
Однако Composition API — это продвинутая концепция. Я не думаю, что это может сразу заменить реальный способ написания компонентов. Более того, мы по-прежнему будем сталкиваться с устаревшим кодом, написанным до Vue 3, поэтому наши предыдущие знания по-прежнему будут полезны.
Я уже обсуждал этот вопрос в предыдущей статье, но очень важно помнить об этом: не всем посчастливилось открыть для себя 3.0 после двух лет практики Vue почти ежедневно.
Некоторые люди начнут использовать Vue начиная с версией 3.0, и совершенно новое API, может значительно увеличивает и без того большую начальную стоимость разработки. Теперь новичок должен занять еще и это в дополнение к «классическому» API options.
Как вы думаете, новое API должно ли быть представлено новичкам? Я лично считаю, что оно должно быть похоже на Vuex или Vue Router (то есть быть опциональным), и использоваться как дополнительный инструмент.
Поделитесь своими мыслями!
Что вы думаете о новом Composition API?
Готовы ли вы использовать его, как только выйдет 3.0?
Пожалуйста, расскажите об этом всем остальным и позвольте нам все это обсудить 🙂
Теперь, когда предмет теоретически представлен, что дальше? Я запачкаю руки и попробую по максимуму использовать Composition API, начав в следующей статье с сеанса рефакторинга!
Напоминает машинный перевод материала какого-то американца. Это просто ощущение.