Интересные новинки Vue 3
Вместо предисловия
Vue используется во всех проектах FunCorp. Мы внимательно следим за развитием фреймворка, постоянно улучшаем процесс разработки и внедряем лучшие практики. И, конечно же, мы не могли пройти мимо и не перевести статью Филиппа Раковски, сооснователя VueStorefront, про новые фичи Vue 3, серьёзно влияющие на написание кода.
В прошлый раз мы рассматривали фичи, которые влияют на производительность Vue 3. Нам уже известно, что приложения, написанные на новой версии фреймворка, работают очень быстро, но производительность — не самое важное изменение. Для большинства разработчиков намного важнее то, как Vue 3 повлияет на стиль написания кода.
Как вы уже догадались, во Vue 3 появится много крутых фич. К счастью, команда Vue добавила больше улучшений и дополнений, чем ломающих изменений. Благодаря этому большинство разработчиков, знающих Vue 2, должны быстро освоиться в новом синтаксисе.
Давайте начнём с API, о котором многие из вас могли слышать.
Composition API
Composition API — самая обсуждаемая и упоминаемая фича следующей мажорной версии Vue. Синтаксис Composition API предоставляет абсолютно новый подход к организации и переиспользованию кода.
Сейчас мы создаём компоненты с синтаксисом, который называется Options API. Для того чтобы добавить логику, мы создаём свойства (опции) в объекте компонента, например data, methods, computed и т.д. Основным недостатком данного подхода является то, что это не JavaScript-код как таковой. Вам необходимо точно знать, какие опции доступны в шаблоне и каким будет поведение this. Компилятор Vue преобразует свойства в работающий JavaScript-код за вас. Из-за этой особенности мы не можем в полной мере пользоваться автодополнением или проверкой типов.
Composition API решает эту проблему и даёт возможность использовать механизмы, доступные через опции, с помощью обыкновенных JavaScript-функций.
Команда Vue описывает Composition API как «дополнительный, основанный на функциях API, позволяющий гибко использовать композицию в логике компонента». Код, написанный с помощью нового API, лучше читается, что делает его более лёгким для понимания.
Чтобы разобраться в том, как работает новый синтаксис, рассмотрим пример простого компонента.
Разобьём код на части и разберём, что же здесь происходит.
Как я уже упоминал выше, Composition API представляет опции компонента как функции, следовательно, первым делом мы должны импортировать необходимые функции. В этом примере нам нужно создать реактивное свойство с помощью ref, вычисляемое с помощью computed и получить доступ к хуку mounted жизненного цикла с помощью функции onMounted.
Возможно, у вас возникнет вопрос: что это за таинственный метод setup?
Если коротко, setup — просто функция, которая передаёт свойства и функции в шаблон. Мы описываем все реактивные и вычисляемые свойства, хуки жизненного цикла и всех наблюдателей в функции setup, а затем возвращаем их, чтобы использовать в шаблоне.
К тому, что мы не вернём из setup, доступа в шаблоне не будет.
Реактивное свойство count инициализируем с помощью функции ref. Она принимает примитив или объект и возвращает реактивную ссылку. Переданное значение будет сохранено в свойстве value созданной ссылки. Например, если мы хотим получить доступ к значению count, нам необходимо явно обратиться к count.value.
Так мы объявляем вычисляемое свойство double и функцию increment.
C помощью хука onMounted мы выводим в консоль сообщение после монтирования компонента для демонстрации такой возможности.
Чтобы свойства count и double и метод increment были доступны в шаблоне, возвращаем их из метода setup.
И вуаля! У нас есть доступ к свойствам и методам из setup, точно так же, как если бы они были объявлены через старый Options API.
Это простой пример, подобное можно было бы легко написать и с помощью Options API.
Но преимущество нового Composition API не столько в возможности писать код в другом стиле, сколько в возможностях, открываемых для повторного использования логики.
Переиспользование кода с Composition API
Давайте подробнее рассмотрим преимущества нового Composition API, например, для переиспользования кода. Сейчас, если мы хотим использовать какой-то кусок кода в нескольких компонентах, у нас есть два варианта: миксины (mixins) и слоты с ограниченной областью видимости (scoped slots). Оба варианта имеют свои недостатки.
Мы хотим извлечь функциональность счётчика и переиспользовать его в других компонентах. Вот пример, как это может быть сделано с помощью существующего и с помощью нового API.
Для начала рассмотрим реализацию с использованием миксинов.
Самая большая проблема такого подхода — мы ничего не знаем о том, что добавляется в наш компонент. Это затрудняет понимание и может приводить к коллизиям с существующими свойствами и методами.
Теперь рассмотрим слоты с ограниченной областью видимости.
При использовании слотов мы в точности знаем, к каким свойствам мы имеем доступ через директиву v-slot, что достаточно просто понять. Недостаток этого подхода в том, что мы можем получить доступ только к данным компонента Counter.
А теперь рассмотрим реализацию с использованием Composition API.
Выглядит гораздо элегантнее, не так ли? Мы не ограничены ни шаблоном, ни областью видимости и точно знаем, какие свойства счётчика доступны. И благодаря тому, что useCounter — просто функция, которая возвращает данные, в качестве приятного бонуса мы получаем автодополнение кода в редакторе. Здесь нет магии, поэтому редактор может помогать нам с проверкой типов и давать подсказки.
Лучше выглядит и использование сторонних библиотек. Например, если мы хотим использовать Vuex, то можем явно импортировать функцию useStore и не засорять прототип Vue свойством this.$store. Этот подход позволяет избавиться от дополнительных манипуляций в плагинах.
Если вы хотите узнать больше о Composition API и его применениях, я рекомендую прочитать документ, в котором команда Vue объясняет причины создания нового API и предлагает кейсы, в которых он пригодится. Также есть замечательный репозиторий с примерами использования Composition API от Thorsten Lünborg, одного из членов команды ядра Vue.
Изменения в конфигурировании и монтировании
В новом Vue есть другие важные изменения в том, как мы создаём и конфигурируем наше приложение. Давайте рассмотрим это на примере.
Сейчас мы используем глобальный объект Vue для конфигурирования и создания новых инстансов Vue. Любое изменение, сделанное нами в объекте Vue, будет затрагивать конечные инстансы и компоненты.
Рассмотрим, как это будет работает во Vue 3.
Как вы уже заметили, конфигурация относится к конкретному инстансу Vue, созданному с помощью createApp.
Это делает наш код более читабельным, снижает возможность появления неожиданных проблем со сторонними плагинами. Сейчас любая сторонняя библиотека, модифицирующая глобальный объект Vue, может повлиять на ваше приложение в неожиданном месте (особенно если это глобальный миксин), что невозможно во Vue 3.
Эти изменения обсуждаются в RFC, и возможно, в будущем реализация будет другой.
Фрагменты
Ещё одна крутая фича, на которую мы можем рассчитывать во Vue 3.
Что такое фрагменты?
В настоящий момент компонент может иметь только один корневой элемент, а это значит, что код ниже работать не будет.
Причиной является то, что инстанс Vue, скрывающийся за каждым компонентом, может быть прикреплён только к одному элементу DOM. Сейчас существует способ создать компонент с несколькими корневыми элементами: для этого необходимо написать компонент в функциональном стиле, которому не нужен собственный инстанс Vue.
Оказывается, такая же проблема существует и в React-сообществе, решена она была с помощью виртуального элемента Fragment.
Несмотря на то, что Fragment выглядит как обычный DOM-элемент, он виртуальный и не будет создаваться в DOM-дереве. С этим подходом мы можем использовать функциональность одного корневого элемента без создания лишнего элемента в DOM.
Сейчас вы можете использовать фрагменты и во Vue 2, но с помощью библиотеки vue-fragments, а во Vue 3 они будут работать из коробки!
Suspense
Еще одна отличная идея из экосистемы React, которая будет реализована во Vue 3, — это Suspense.
Suspense приостанавливает рендеринг компонента и отображает заглушку до выполнения определённых условий. На конференции Vue London Эван Ю вскользь затронул Suspense и показал API, который мы можем ожидать в будущем. Suspense-компонент будет иметь 2 слота: для контента и для заглушки.
Заглушка будет отображаться до тех пор, пока компонент не будет готов. Компонент Suspense также может ожидать загрузку асинхронного компонента или выполнения каких-то асинхронных действий в setup-функции.
Несколько v-models
v-model — это директива, с помощью которой можно использовать двусторонний биндинг. Мы можем передать реактивное свойство и изменить его внутри компонента.
Нам она хорошо известна по работе с элементами форм.
Но знали ли вы, что v-model можно использовать с любым компонентом? Под капотом v-model является лишь пробросом параметра value и прослушиванием события input.
Переписать предыдущий пример с использованием этого синтаксиса можно следующим образом:
Можно даже изменить названия свойства и события по умолчанию с помощью опции model:
Как видно, директива v-model может быть очень полезным «синтаксическим сахаром», если мы хотим использовать двусторонний биндинг в наших компонентах. К сожалению, на компонент может быть лишь одна v-model.
К счастью, во Vue 3 эта проблема будет решена. Мы сможем передать имя в v-model и использовать столько v-model, сколько необходимо.
Эти изменения обсуждаются в RFC, и возможно, в будущем реализация будет другой.
Portals
Порталы — это компоненты, созданные для рендера контента вне иерархии текущего компонента. Это тоже одна из возможностей, реализованных в React. В документации React порталы описываются следующим образом: «Порталы позволяют рендерить дочерние элементы в DOM-узел, который находится вне DOM-иерархии родительского компонента».
Порталы отлично подходят для реализации таких компонентов, как модальные окна, попапы и всех тех, которые необходимо отобразить поверх страницы.
При использовании порталов вы можете быть уверены, что стили родительского компонента не повлияют на дочерний. Это также избавит вас от грязных хаков с z-index.
Для каждого портала нам необходимо указать место назначения, в котором должен отображаться контент портала.
Ниже представлен вариант реализации на библиотеке portal-vue, которая добавляет порталы во Vue 2.
А во Vue 3 данная фича будет из коробки.
Новое API пользовательских директив
API пользовательских директив немного изменится во Vue 3, чтобы больше соответствовать жизненному циклу компонента. Создание директив станет более интуитивным, а значит, и более простым для понимания и изучения новичками.
Сейчас объявление пользовательской директивы выглядит так:
А во Vue 3 будет выглядеть так:
Несмотря на то, что это ломающие изменения, они могут быть использованы с совместимой сборкой Vue.
Этот API так же обсуждается и может измениться в будущем.
Резюме
Рядом со значительным нововведением — Composition API — мы можем найти несколько улучшений поменьше. Очевидно, что Vue движется в сторону улучшения опыта разработчика, к упрощению и интуитивизации API. Так же круто видеть, что команда Vue решила добавить в ядро фреймворка много идей, которые уже реализованы в сторонних библиотеках.
Список выше содержит только наиболее важные улучшения и изменения API. Если вам захотелось узнать и о других, загляните в репозиторий RFC.
# Введение
# Почему появился Composition API?
Прежде чем приступать к изучению этого раздела документации необходимо понимать как базовые основы Vue, так и принципы создания компонентов.
Создание компонентов Vue позволяет извлекать повторяющиеся части интерфейса, вместе со связанной функциональностью, в переиспользуемые части кода. Этот подход добавляет приложению достаточно много с точки зрения удобства обслуживания и гибкости. Однако, коллективный опыт показал, что этого всё ещё может быть недостаточно, если приложение становится действительно большим — когда счёт идёт на несколько сотен компонентов. Когда приходится работать с такими большими приложениями — возможность разделения и переиспользования кода становится крайне важна.
Представим, что в приложении есть компонент, который отображает список репозиториев конкретного пользователя. Поверх него, нужно реализовать функциональность поиска и фильтрации. Компонент, управляющий подобным отображением, может выглядеть так:
Этот компонент имеет несколько обязанностей:
Пример большого компонента, в котором сгруппированы по цвету его логические блоки.
Подобная фрагментация усложняет понимание и поддержание таких сложных компонентов. Разделение по опциям делает менее заметными логические блоки используемые в них. Кроме того, при работе над одной логической задачей приходится постоянно «прыгать» между блоками в поисках соответствующего кода.
Было бы удобнее, если соответствующий логическому блоку код можно разместить рядом. И это именно то, чего позволяет добиться Composition API.
# Основы Composition API
# Опция компонента setup
Опция setup должна быть функцией, которая принимает аргументами props и context (о которых подробнее поговорим дальше). Кроме того, всё что возвращается из функции setup будет доступно для остальных частей компонента (вычисляемых свойств, методов, хуков жизненного цикла и т.д.), а также в шаблоне компонента.
Добавим setup в компонент:
Извлечём логику первого логического блока (отмеченной как «1» в исходном примере).
Начнём с самых очевидных частей:
Начало положено! Пока что ещё не всё работает, потому что переменная repositories не реактивна. С точки зрения пользователя, список репозиториев будет оставаться пустым. Давайте это исправим!
# Реактивные переменные с помощью ref
Наличие объекта-обёртки вокруг любого значения позволяет безопасно использовать его в любой части приложения, не беспокоясь о потере реактивности где-то по пути.
Другими словами, ref создаёт реактивную ссылку к значению. Концепция работы со ссылками используется повсеместно в Composition API.
Возвращаясь к примеру, создадим реактивную переменную repositories :
Готово! Теперь каждый вызов getUserRepositories будет изменять repositories и обновлять вид блока, чтобы отобразить изменения. Компонент станет выглядеть так:
Начнём с хука жизненного цикла.
# Использование хуков жизненного цикла внутри setup
Эти функции принимают аргументом коллбэк, который выполнится при вызове компонентом хука жизненного цикла.
Добавим его в функцию setup :
# Отслеживание изменений с помощью watch
Простой пример, чтобы понять как это работает:
Эквивалент при использовании Options API:
Более подробную информацию о watch можно найти в продвинутом руководстве.
API Composition Vue 3 и разделение проблем
Я всегда думаю, что лучше всего рассматривать вещи в какм либо контексте. Вот почему я написал первую статью о моем взгляде на состояние 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:
Как видите, вам нужно явно получить доступ к значению ref при манипулировании им в JS. Это может немного беспокоит, но вам не придется делать это в шаблоне и вы можете напрямую обращаться к значению, как мы увидим позже.
Пожалуйста, взгляните на справочник Reactivity API для получения дополнительной информации о том, что в нем содержится.
Хорошо, но как эти два API сочетаются друг с другом? Давайте посмотрим на это на примере. Сначала мы напишем компонент с помощью Options API, а затем сделаем это а-ля Vue 3.
Допустим, у нас есть компонент, управляющий загрузкой и отображением сообщений в блоге, минималистичная его версия может быть такой:
Используя новые предоставленные инструменты, мы можем поместить всю логику в setup:
Это может показаться не очень полезным в компоненте с небольшим количеством логики, но это уже помогает разработчикам отслеживать различные части без прокрутки между параметрами экземпляра Vue. Позже в этой и следующих статьях мы увидим, как извлечь из этого максимальную пользу.
Мы также можем извлечь логику, создав отдельный модуль (posts.js), управляющий данными и предоставляющий полезную информацию:
Тогда наш компонент будет управлять шаблоном только на основе данных, предоставленных модулем. Полное разделение проблем:
Опять же, это помогает прояснить ваш код и отделить намерение от реализации, что всегда приятно.
Возможно, вы уже думали об этом, но этот способ создания модулей может помочь нам повторно использовать логику!
Повторное использование логики
У нас уже есть некоторые инструменты, которые помогают нам создавать логику, которая будет использоваться многими компонентами. Например, миксины позволяют писать любые параметры экземпляра 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, начав в следующей статье с сеанса рефакторинга!
Composition API RFC
Перевод RFC в котором предложено и описывается Composition API. Оригинал: Composition API RFC
Обзор
Представляем Composition API: набор аддитивных API на основе функций, которые позволяют гибко комбинировать логику компонентов.
Базовый пример
Мотивация
Логическое повторное использование кода (Reuse) & Организация кода
Мы все любим, как с помощью Vue можно очень легко создавать небольшие и средние приложения на одном дыхании. Но сегодня, по мере роста популярности Vue, многие пользователи также используют Vue для создания крупномасштабных проектов — проектов, которые выполняются и поддерживаются в течение длительного периода времени командой из нескольких разработчиков. За прошедшие годы мы стали свидетелями того, как некоторые из этих проектов испытывают проблемы в рамках модели программирования, используемой текущим Vue API. Проблемы могут быть разделены на две категории:
API-интерфейсы, предложенные в этом RFC, предоставляют пользователям больше гибкости при организации кода компонента. Вместо того, чтобы заставлять всегда организовывать код по заранее определенным опциям, код теперь может быть организован в функциях, каждая из которых будет реализовывать определенный функционал. API также упрощают извлечение и повторное использование логики между компонентами или даже внешними компонентами. Мы покажем, как эти цели достигаются в разделе «Детальный дизайн».
Улучшенное представление типов
Другой распространенный запрос от разработчиков, работающих над большими проектами, — лучшая поддержка TypeScript. Нынешний Vue API создает некоторые проблемы, когда речь заходит об интеграции с TypeScript, в основном из-за того, что Vue полагается на контекст this для предоставления свойств, и что использование this в компоненте Vue немного более магически (сложнее), чем в простом JavaScript (например, this внутри функций, вложенных в methods, указывает на экземпляр компонента, а не на объект methods). Другими словами, существующий Vue API просто не был разработан с учетом логического вывода типов, и это создает большую сложность при попытке адаптировать его для работы с TypeScript.
Большинство пользователей, которые сегодня используют Vue с TypeScript, используют библиотеку vue-class-component, которая позволяет создавать компоненты как классы TypeScript (с помощью декораторов). При разработке 3.0 мы пытались предоставить встроенный Class API для лучшего решения проблем типизации в предыдущем (удаленном) RFC. Однако, как мы заметили, что для того, чтобы Class API мог разрешать проблемы с типизацией, он должен полагаться на декораторы — что является очень нестабильным второго этапа предложения (proposal) с большой неопределенностью в отношении деталей его реализации. Это делает его довольно рискованным для реализации. (Подробнее о проблемах типов Class API здесь)
Для сравнения, API, предложенные в этом RFC, используют в основном простые переменные и функции, которые естественно дружественны к типу. Код, написанный с использованием предложенных API-интерфейсов, может иметь полный вывод типа без необходимости подсказок типа вручную. Это также означает, что код, написанный с использованием предложенных API-интерфейсов, будет выглядеть практически одинаково в TypeScript и обычном JavaScript, поэтому даже пользователи, не использующие TypeScript, потенциально смогут извлечь выгоду из типов для лучшей поддержки IDE.
Детальный дизайн
Введение в API
Вместо того, чтобы вводить новые концепции, предлагаемое API больше направлено на то, чтобы представить основные возможности Vue, такие как создание и наблюдение за реактивным состоянием, в качестве отдельных функций. Здесь мы представим ряд наиболее фундаментальных API и способы их использования вместо опций версии 2.x для выражения внутрикомпонентной логики. Обратите внимание, что этот раздел посвящен ознакомлению с основными идеями, поэтому в нем подробно не рассматривается каждое новое API. Полные спецификации API можно найти в разделе API Reference.
Реактивное состояние и побочные эффекты
Начнем с простой задачи: объявление какой-то реактивной переменной.
reactive является эквивалентом текущего API-интерфейса Vue.observable() в 2.x, переименованного во избежание путаницы с наблюдаемыми (observables) объектами RxJS. Здесь возвращаемая переменная state является реактивным объектом, с которым все пользователи Vue должны быть знакомы.
Основным вариантом использования реактивной переменной в Vue является то, что мы можем использовать ее во время рендеринга. Благодаря отслеживанию зависимостей представление автоматически обновляется при изменении реактивной переменной. Рендеринг чего-либо в DOM считается «побочным эффектом»: наша программа изменяет переменную, внешнею по отношению к самой программе (DOM). Чтобы применить и автоматически повторно применить побочный эффект в зависимости от реактивной переменной, мы можем использовать watch API:
watch ожидают функцию, которая применяет желаемый побочный эффект (в нашем случае, определение innerHTML). Она немедленно выполняет функцию и отслеживает все свойства реактивной переменной, которую она использовала во время выполнения, в качестве зависимостей. Здесь, state.count будет отслеживаться как зависимость для этого наблюдателя после первоначального выполнения. Когда в будущем будет изменена state.count, внутренняя функция снова будет выполнена.
Это сама суть системы реактивности Vue. Когда вы возвращаете объект из data() в компоненте, он внутренне становится реактивным с помощью reactive(). Шаблон компилируется в функцию рендеринга (воспринимается как более эффективный innerHTML), которая использует эти реактивные свойства.
Продолжая приведенный выше пример, мы обработаем пользовательский ввод:
Но с системой шаблонов Vue нам не нужно связываться с innerHTML или вручную подключать прослушиватели событий (event listeners). Давайте упростим пример с помощью гипотетического метода renderTemplate, чтобы мы могли сосредоточиться на реактивности:
Вычисляемое состояние и Refs
Иногда нам нужно состояние, которое зависит от другого состояния — в Vue это обрабатывается с помощью вычисляемых свойств (computed). Чтобы напрямую создать вычисляемое значение, мы можем использовать computed API:
Что вернет computed? Предположим, как computed реализован внутри, это будет что-то вроде такого:
Но мы знаем, что это не сработает: если значение value является примитивным типом (например число), то его связь с логикой обновления внутри computed будет потеряна после его возврата (оператор return). Это потому, что примитивные типы JavaScript передаются по значению, а не по ссылке:
Та же проблема возникает, когда объекту присваивается значение как свойство. Реактивное значение не будет очень полезным, если оно не сможет сохранить свою реактивность при назначении в качестве свойства или при возврате из функций. Чтобы убедиться, что мы всегда можем прочитать последнее значение вычисления, нам нужно обернуть фактическое значение в объекте и вместо этого вернуть этот объект:
Кроме того, нам также необходимо перехватывать операции чтения/записи в свойстве объекта .value для отслеживания зависимостей и уведомления об изменениях (здесь для простоты код опущен). Теперь мы можем передавать вычисленное значение по ссылке, не беспокоясь о потере реактивности. Компромисс в том, что для получения последнего значения нам теперь нужно получить к нему доступ через .value:
Здесь double — это объект, который мы называем «ref», поскольку он служит реактивной ссылкой на внутреннее значение, которое он содержит.
Возможно, вы знаете, что в Vue уже есть понятие «refs», но только для ссылки на элементы DOM или экземпляры компонентов в template («refs шаблона»). Прочтите это, чтобы увидеть, как новая система ссылок refs может использоваться как для логического состояния, так и для шаблона ссылок refs.
В дополнение к вычисляемым ссылкам, мы также можем напрямую создавать простые изменяемые ссылки, используя ref API:
Использование Ref
Мы можем выставить ref как свойство в контексте визуализации. Внутри Vue будет выполнять специальную обработку для refs, так что, когда refs встречается в контексте рендеринга, контекст напрямую раскрывает свое внутреннее значение. Это означает, что в шаблоне мы можем напрямую написать << count >> вместо <
Вот версия того же примера счетчика, использующего ref вместо reactive:
Кроме того, когда ref вкладывается как свойство в реактивный объект, она также автоматически разворачивается при доступе:
Использование в Компонентах
Наш код на данный момент уже предоставляет рабочий UI, который может обновляться на основе пользовательского ввода, но код выполняется только один раз и не может использоваться повторно. Если мы хотим повторно использовать логику, следующим разумным шагом будет рефакторинг его в функцию:
Обратите внимание, что приведенный выше код не зависит от наличия экземпляра компонента. Действительно, API, представленные до сих пор, могут использоваться вне контекста компонентов, что позволяет нам использовать систему реактивности Vue в более широком диапазоне сценариев.
Теперь, если мы оставим задачи вызова setup(), создания watcher и рендеринга template в фреймворке, мы можем определить компонент только с помощью функции setup() и template:
Это знакомый нам формат однофайлового компонента, только с логической частью ( —>
Примечание: исходный код, вероятно, может быть улучшен в нескольких местах, но мы показываем его в последнем коммите (на момент написания статьи) без изменений, чтобы предоставить пример фактического рабочего кода, который мы написали сами.
Было бы намного лучше, если бы мы могли разместить код, основываясь на логической задачей который он решает. И это именно то, что для чего мы создали Composition API. Функцию «Создать новую папку» можно теперь записать так:
Обратите внимание на то, как вся логика, связанная с функцией создания новой папки, теперь расположена и помещена в одну функцию. Функция также несколько самодокументируется из-за своего описательного имени. Это то, что мы называем композиционной функцией (composition function). Рекомендуется начинать имя функции с use, чтобы указать, что это составная функция. Этот шаблон может быть применен ко всем другим логическим проблемам в компоненте, что приводит к ряду красиво отделенных функций:
Это сравнение исключает операторы импорта и функцию setup(). Полный компонент, повторно реализованный с использованием Composition API, можно найти здесь.
Код для каждой логической задачи теперь объединен в композиционной функции. Это значительно снижает необходимость в постоянных «скачках» при работе с большим компонентом. Компоновочные функции также могут быть свернуты в редакторе, чтобы сделать сканирование компонента намного проще:
Функция setup() теперь в основном служит точкой входа, где вызываются все функции композиции:
Конечно, это код, который нам не нужно было писать при использовании API с опциями. И обратите внимание, что функция setup почти читается как словесное описание того, что пытается сделать компонент — это информация, так же полностью отсутствовала в версии, основанной на опциях. Вы можете четко видеть поток зависимостей между композиционными функциями на основе передаваемых аргументов. Наконец, оператор return служит единственным местом для проверки того, что доступно шаблону.
При одинаковой функциональности компонент, определенный с помощью опций, и компонент, определенный с помощью композиционных функций, проявляют два разных способа выражения одной и той же базовой логики. API на основе параметров вынуждает нас организовывать код на основе типов опций (option types), в то время как API-интерфейс Composition позволяет нам организовывать код на основе логических соображений (logical concerns).
Извлечение и повторное использование логики
Composition API чрезвычайно гибок, когда дело доходит до извлечения и повторного использования логики между компонентами. Вместо того, чтобы полагаться на магический контекст this, функция композиции опирается только на свои аргументы и глобально импортированные API-интерфейсы Vue. Вы можете повторно использовать любую часть вашей компонентной логики, просто экспортировав ее как функцию. Вы даже можете получить эквивалент extends, экспортируя всю функцию setup.
Давайте посмотрим на пример: отслеживание положения мыши.
Вот как компонент может использовать эту функцию:
В версии Composition API примера файлового эксплорера мы перенесли некоторый служебный код (например, usePathUtils и useCwdUtils) во внешние файлы, потому что мы нашли их полезными для других компонентов.
Аналогичное повторное использование логики также может быть достигнуто с использованием существующих шаблонов, таких как миксины, компоненты высшего порядка или компоненты без рендеринга (через scoped слоты). В интернете достаточно информации, объясняющей эти паттерны, поэтому мы не будем их здесь подробно описывать. Более общая идея заключается в том, что каждый из этих шаблонов имеет соответствующие недостатки по сравнению с функциями композиции:
По сравнению с Composition API:
Использование вместе с существующим API
Composition API может использоваться вместе с существующим API на основе опций.
Разработка плагинов
Многие плагины Vue сегодня внедряют свойства в this. Например, Vue Router внедряет this.$route и this.$router, а Vuex внедряет this.$store. Это усложняет получение типов, так как каждый плагин требует, чтобы пользователь увеличивал типизацию Vue для введенных свойств.
При использовании Composition API this не используется. Вместо этого плагины будут использовать provide и inject внутри и предоставят функцию композиции. Ниже приведен гипотетический код для плагина:
И код использования:
Обратите внимание, что store также может быть предоставлено с помощью provide уровня приложения, предложенного в Global API change RFC, но API useStore в компоненте где он используется будет таким же.
Недостатки
Накладные расходы по представлению Refs
Ref технически является единственной «новой» концепцией, представленной в этом RFC. Оно введено для того, чтобы создавать реактивные переменные, не полагаясь на доступ к this. Недостатки:
Мы обсудили, возможно ли полностью избежать концепции Ref и использовать только реактивные объекты (reactive), однако:
Ref против Reactive
Понятно, что пользователи могут запутаться в отношении того, что использовать ref или reactive. Первое, что нужно знать, это то, что вам нужно будет понять и то, и другое, чтобы эффективно использовать Composition API. Использование одного из них, скорее всего, приведет к эзотерическим обходным путям или к новым колесам.
Разницу между использованием ref и reactive можно несколько сравнить с тем, как вы будете писать стандартную логику JavaScript:
Однако проблема с использованием только reactive заключается в том, что потребитель композиционной функции должен постоянно сохранять ссылку на возвращаемый объект, чтобы сохранить реактивность. Объект не может быть разрушен или распространен:
API toRefs предназначен для решения этого ограничения — он преобразует каждое свойство реактивного объекта в соответствующий ref:
Подводя итог, можно выделить два жизнеспособных стиля:
На данном этапе мы полагаем, что еще слишком рано называть лучшей практикой использование ref, а не reactive. Мы рекомендуем вам придерживаться стиля, который лучше соответствует вашей ментальной модели, из двух вариантов выше. Мы будем собирать отзывы пользователей в реальном мире и в конечном итоге предоставим более четкие рекомендации по этой теме.
Детально о return в setup()
Некоторые пользователи выражают беспокойство по поводу того, что выражение return в setup() является многословным и ощущается как бесполезный шаблон.
Мы считаем, что явное использование return полезно для удобства поддержки кода. Это дает нам возможность явно контролировать то, что раскрывается в шаблоне, и служит отправной точкой при трассировке, где свойство шаблона определено в компоненте.
Были предложения автоматически выставлять переменные, объявленные в setup(), делая оператор return необязательным. Опять же, мы не думаем, что он должен быть по умолчанию, так как это противоречит интуиции стандартного JavaScript. Однако, есть возможные способы сделать его использование менее рутинным в пользовательском пространстве:
Больше гибкости требует больше дисциплины
Многие пользователи отмечают, что, хотя интерфейс Composition API обеспечивает большую гибкость в организации кода, он также требует большей дисциплины от разработчика, чтобы «все сделать правильно». Некоторые опасаются, что API приведет к созданию спагетти-кода в неопытных руках. Другими словами, хотя интерфейс Composition API повышает верхнюю границу качества кода, он также понижает нижнюю границу.
Мы согласны с этим в определенной степени. Тем не менее, мы считаем, что:
Некоторые пользователи использовали контроллеры Angular 1 в качестве примеров того, как проект может привести к плохо написанному коду. Самое большое различие между Composition API и контроллерами Angular 1 заключается в том, что Composition API не зависит от общего контекста scope. Это значительно упрощает разделение логики на отдельные функции, что является основным механизмом организации кода JavaScript.
Любая JavaScript-программа начинается с входного файла (воспринимается как setup() для программы). Мы должны организовывать программу, разбивая ее на функции и модули, исходя из логических соображений. Composition API позволяет нам делать то же самое для кода компонента Vue. Другими словами, навыки написания хорошо организованного кода JavaScript напрямую влияют на навыки написания хорошо организованного кода Vue при использовании Composition API.
Стратегия последовательного перехода
Мы намерены выпустить API как встроенное в 3.0. Он будет использоваться вместе с существующими опциями 2.x.
Для пользователей, которые предпочитают использовать исключительно Composition API в приложении, можно будет указать флаг во время компиляции, чтобы отбросить код, используемый только для параметров 2.x, и уменьшать размер библиотеки. Однако это совершенно необязательно.
API будет позиционироваться как расширенная функция, поскольку проблемы, на решение которых он нацелен, появляются главным образом в крупномасштабных приложениях. Мы не намерены пересматривать документацию, чтобы использовать ее по умолчанию. Вместо этого он будет иметь свой собственный выделенный раздел в документации.
Приложение
Проблемы типов с Class API
Основная цель введения Class API состояла в том, чтобы предоставить альтернативный API, который поставляется с лучшей поддержкой вывода TypeScript. Однако тот факт, что компоненты Vue должны объединять свойства, объявленные из нескольких источников, в один контекст this, создает некоторую проблему даже с API на основе классов.
Одним из примеров является типы в props. Чтобы объединить props с this, мы должны либо использовать обобщенные аргументы для класса компонента, либо использовать декоратор.
Вот пример использования обобщенных аргументов:
Поскольку интерфейс, передаваемый универсальному аргументу, находится только в области типов, пользователю все равно необходимо предоставить объявление props во время выполнения для соединения с this. Это двойное объявление является излишним и неудобным.
Мы рассмотрели использование декораторов в качестве альтернативы:
Использование декораторов создает зависимость от спецификации этапа 2 с большим количеством неопределенностей, особенно когда текущая реализация TypeScript полностью не синхронизирована с предложением TC39. Кроме того, в this.$props нет способа выставить типы props, объявленные с помощью декораторов, что нарушает поддержку TSX. Пользователи могут также предположить, что они могут объявить значение по умолчанию для @prop message: string = ‘foo’, когда технически это просто не может работать должным образом.
Кроме того, в настоящее время нет способа использовать контекстную типизацию для аргументов методов класса — это означает, что аргументы, передаваемые в функцию рендеринга класса render, не могут иметь предполагаемые типы, основанные на других свойствах класса.
Сравнение с React Hooks
API на основе функций обеспечивает тот же уровень возможностей логической композиции, что и React Hooks, но с некоторыми важными отличиями. В отличие от React Hooks, функция setup() вызывается только один раз. Это означает, что код, использующий Composition API:
Мы признаем креативность React Hooks, и это является основным источником вдохновения для этого RFC. Тем не менее, проблемы, упомянутые выше, существуют в его дизайне, и мы заметили, что модель реактивности Vue обеспечивает обходной путь.
Сравнение с Svelte
Несмотря на совершенно разные подходы, Composition API и подход на основе компиляторов Svelte 3 фактически концептуально имеют много общего. Вот пример:
Svelte
Код Svelte выглядит более лаконичным, потому что во время компиляции он делает следующее:







