Что такое "Composable"?
В контексте приложений Vue «компонуемая» — это функция, которая использует API композиции Vue для инкапсуляции и повторного использования логики с отслеживанием состояния .
При создании клиентских приложений нам часто приходится повторно использовать логику для решения общих задач. Например, нам может потребоваться форматировать даты во многих местах, поэтому для этого мы извлекаем функцию многократного использования. Эта функция форматирования инкапсулирует логику без сохранения состояния : она принимает некоторые входные данные и немедленно возвращает ожидаемый результат. Существует множество библиотек для повторного использования логики без сохранения состояния — например, lodash и date-fns , о которых вы, возможно, слышали.
Напротив, логика с отслеживанием состояния предполагает управление состоянием, которое меняется с течением времени. Простым примером может быть отслеживание текущего положения мыши на странице. В реальных сценариях это также может быть более сложная логика, такая как сенсорные жесты или состояние подключения к базе данных.
Пример трекера мыши
Если бы мы реализовали функцию отслеживания мыши с помощью Composition API непосредственно внутри компонента, это выглядело бы так
Но что, если мы хотим повторно использовать одну и ту же логику в нескольких компонентах? Мы можем извлечь логику во внешний файл как составную функцию
А вот как его можно использовать в компонентах
Как мы видим, основная логика осталась идентичной — все, что нам нужно было сделать, это переместить ее во внешнюю функцию и вернуть состояние, которое должно быть раскрыто. Как и внутри компонента, в составных объектах вы можете использовать весь спектр функций Composition API. Эту же useMouse() функциональность теперь можно использовать в любом компоненте.
Самое интересное в составных объектах то, что их можно также вкладывать: одна составная функция может вызывать одну или несколько других составных функций. Это позволяет нам составлять сложную логику с использованием небольших изолированных модулей, подобно тому, как мы составляем целое приложение с использованием компонентов. Фактически, именно поэтому мы решили назвать коллекцию API, которые делают этот шаблон возможным, Composition API.
Например, мы можем извлечь логику добавления и удаления прослушивателя событий DOM в отдельный составной объект
И теперь нашу useMouse() компоновку можно упростить до
Обратите внимание
Каждый вызов экземпляра компонента useMouse() создаст свои собственные копии xи yсостояние, чтобы они не мешали друг другу. Если вы хотите управлять общим состоянием компонентов, прочтите главу «Управление состоянием» .
Пример асинхронного состояния
Composable useMouse() не принимает никаких аргументов, поэтому давайте посмотрим на другой пример, в котором он используется. При асинхронном извлечении данных нам часто приходится обрабатывать разные состояния: загрузка, успех и ошибка:
Было бы утомительно повторять этот шаблон в каждом компоненте, которому необходимо получить данные. Давайте извлечем его в составной файл:
Теперь в нашем компоненте мы можем просто сделать:
Принятие реактивного состояния
useFetch() принимает в качестве входных данных статическую строку URL-адреса, поэтому выборка выполняется только один раз, а затем выполняется. Что, если мы хотим, чтобы он выполнялся повторно при каждом изменении URL-адреса? Чтобы добиться этого, нам нужно передать реактивное состояние в составную функцию и позволить составной функции создавать наблюдатели, которые выполняют действия с использованием переданного состояния.
Например, useFetch() должна иметь возможность принимать ссылку:
Или примите функцию получения:
Мы можем провести рефакторинг нашей существующей реализации с помощью API watchEffect() и toValue()
toValue() это API, добавленный в версии 3.3. Он предназначен для нормализации ссылок или геттеров в значения. Если аргумент является ссылкой, он возвращает значение ссылки; если аргумент является функцией, он вызовет функцию и вернет ее возвращаемое значение. В противном случае он возвращает аргумент как есть. Он работает аналогично unref(), но с особым подходом к функциям.
Обратите внимание, что toValue(url) это вызывается внутри обратного вызова watchEffect. Это гарантирует, что toValue() наблюдатель отслеживает любые реактивные зависимости, к которым осуществляется доступ во время нормализации.
Эта версия useFetch() теперь принимает статические строки URL, ссылки и методы получения, что делает ее гораздо более гибкой. Эффект наблюдения запустится немедленно и будет отслеживать любые зависимости, к которым осуществляется доступ во время toValue(url). Если никакие зависимости не отслеживаются (например, URL-адрес уже является строкой), эффект выполняется только один раз; в противном случае он будет запускаться повторно при каждом изменении отслеживаемой зависимости.
Соглашения и лучшие практики
Именование
Компонуемые функции принято называть именами в верблюжьем регистре, начинающимися с «использования».
Входные аргументы
Компонуемый объект может принимать аргументы ref или getter, даже если он не полагается на них для реактивности. Если вы пишете компонуемый объект, который может использоваться другими разработчиками, рекомендуется предусмотреть случай, когда входные аргументы являются ссылками или методами получения, а не необработанными значениями. toValue() Для этой цели пригодится служебная функция
Если ваш компонуемый объект создает реактивные эффекты, когда входные данные являются ссылкой или геттером, обязательно либо явно наблюдайте за ссылкой/геттером с помощью watch(), либо вызывайте toValue() внутри a watchEffect(), чтобы он правильно отслеживался.
Реализация useFetch(), обсуждавшаяся ранее, представляет собой конкретный пример составного объекта, который принимает ссылки, методы получения и простые значения в качестве входного аргумента.
Возвращаемые значения
Вы, наверное, заметили, что мы использовали исключительно ref() вместо reactive() в составных объектах. Рекомендуемое соглашение заключается в том, чтобы компонуемые объекты всегда возвращали простой, нереактивный объект, содержащий несколько ссылок. Это позволяет его деструктурировать на компоненты, сохраняя при этом реакционную способность:
Возврат реактивного объекта из составного объекта приведет к тому, что такие деструктуры потеряют связь реактивности с состоянием внутри составного объекта, в то время как ссылки сохранят это соединение.
Если вы предпочитаете использовать возвращаемое состояние из составных объектов в качестве свойств объекта, вы можете обернуть возвращаемый объект reactive() так, чтобы ссылки были развернуты
Побочные эффекты
Можно выполнять побочные эффекты (например, добавлять прослушиватели событий DOM или извлекать данные) в составных объектах, но обратите внимание на следующие правила
- Если вы работаете над приложением, использующим серверный рендеринг (SSR), обязательно выполняйте специфичные для DOM побочные эффекты в перехватчиках жизненного цикла после монтирования, например onMounted(). Эти хуки вызываются только в браузере, поэтому вы можете быть уверены, что код внутри них имеет доступ к DOM
- Не забудьте убрать побочные эффекты в onUnmounted(). Например, если составной элемент устанавливает прослушиватель событий DOM, он должен удалить этот прослушиватель, onUnmounted()как мы видели в useMouse()примере. Возможно, будет хорошей идеей использовать компоновку, которая автоматически сделает это за вас, как в useEventListener()примере.
Ограничения использования
Компонуемые объекты следует вызывать только в script setupвиде setup() хука. В этих контекстах их также следует вызывать синхронно. В некоторых случаях вы также можете вызывать их в хуках жизненного цикла, например onMounted()
Эти ограничения важны, поскольку это контексты, в которых Vue может определить текущий экземпляр активного компонента. Доступ к экземпляру активного компонента необходим для того, чтобы
- В нем можно зарегистрировать хуки жизненного цикла.
- С ним можно связать вычисляемые свойства и наблюдатели, чтобы их можно было удалить при размонтировании экземпляра во избежание утечек памяти.
Обратите внимание
script setup— единственное место, где вы можете вызывать компонуемые объекты после использования await. Компилятор автоматически восстанавливает контекст активного экземпляра после асинхронной операции.
Извлечение составных элементов для организации кода
Компонуемые элементы можно извлекать не только для повторного использования, но и для организации кода. По мере роста сложности ваших компонентов вы можете получить компоненты, которые будут слишком большими, чтобы в них можно было ориентироваться и рассуждать. Composition API дает вам полную гибкость для организации кода вашего компонента в более мелкие функции на основе логических соображений:
В некоторой степени вы можете думать об этих извлеченных составных объектах как о службах на уровне компонентов, которые могут взаимодействовать друг с другом.
Использование Composables в API параметров
Если вы используете API-интерфейс Options, компонуемые объекты должны вызываться внутри setup(), а возвращаемые привязки должны быть возвращены setup(), чтобы они были доступны для this шаблона
Сравнение с другими методами
против миксинов
Пользователи, перешедшие на Vue 2, возможно, знакомы с опцией миксинов , которая также позволяет нам извлекать логику компонента в повторно используемые модули. У миксинов есть три основных недостатка:
- 1. Непонятный источник свойств : при использовании множества миксинов становится неясно, какое свойство экземпляра каким миксином внедряется, что затрудняет отслеживание реализации и понимание поведения компонента. Именно поэтому мы рекомендуем использовать шаблон refs + destructure для составных объектов: он делает источник свойств понятным при использовании компонентов.
- 2. Конфликты пространств имен : несколько миксинов от разных авторов потенциально могут регистрировать одни и те же ключи свойств, что приводит к коллизиям пространств имен. С помощью компонуемых объектов вы можете переименовывать деструктурированные переменные, если существуют конфликтующие ключи от разных компонуемых объектов.
- 3. Неявное взаимодействие между миксинами : несколько миксинов, которым необходимо взаимодействовать друг с другом, должны полагаться на общие ключи свойств, что делает их неявно связанными. При использовании составных объектов значения, возвращаемые из одного составного объекта, можно передавать в другой в качестве аргументов, как и в обычных функциях.
По вышеуказанным причинам мы больше не рекомендуем использовать примеси во Vue 3. Эта функция сохранена только в целях миграции и ознакомления.
По сравнению с компонентами без рендеринга
Основное преимущество компонуемых компонентов перед компонентами без рендеринга заключается в том, что компонуемые компоненты не несут дополнительных затрат на экземпляр компонента. При использовании во всем приложении количество дополнительных экземпляров компонентов, созданных шаблоном компонента без рендеринга, может стать заметным снижением производительности.
Рекомендуется использовать составные элементы при повторном использовании чистой логики и использовать компоненты при повторном использовании как логики, так и визуального макета.
Против React Hooks
Если у вас есть опыт работы с React, вы можете заметить, что это очень похоже на пользовательские хуки React. API композиции был частично вдохновлен хуками React, а составные элементы Vue действительно похожи на хуки React с точки зрения возможностей логической композиции. Однако компонуемые элементы Vue основаны на детальной системе реактивности Vue, которая фундаментально отличается от модели выполнения перехватчиков React.