На Reddit прошла интересная дискуссия с 25К+ просмотрами по вопросу предпочтений разработчиков при необходимости управлять глобальным состоянием во Vue 3. Ниже её итоги.
Вопрос автором был поставлен так: Зачем использовать Pinia вместо глобальных ref's?
В своих проектах я использую composable функции с глобальным состоянием, как описано в документации Vue.
Каждая функция представляет собой объект бизнес-логики - например, useShoppingCart
, useAppConfig
и т. д. - и инкапсулирует реактивное состояние и бизнес-логику.
Этот подход часто критикуют и рекомендуют использовать Pinia.
Я знаю о поддержке SSR и возможностях Devtools в Pinia, но это не мой случай - они мне не нужны.
Так почему же, за исключением нужды в SSR и Devtools, я должен использовать Pinia?
Плюсы composable сторов на глобальных рефах очевидны:
Простота
Нативность по отношению к фреймворку.
Отсутствие зависимостей означает отсутствие будущей ситуации "RIP Vuex" с переписыванием 50% кодовой базы проекта.
API Composition выглядит очень зрелым и стабильным и вряд ли сильно изменится в ближайшем будущем (по сравнению с переходом Vue 2 -> Vue 3).
Позволяет использовать всю мощь Reactivity API вместо жесткой Reactive обертки для переменных у Pinia. Разница в производительности может быть огромной.
Минусы?
Было получено на данный момент 36 комментариев, которые можно скомпилировать в следующие выводы:
Большинство согласилось, что если не нужна поддержка SSR и интеграция с Devtools, то работа с Reactivity API напрямую и инкапсуляция реактивного состояния и бизнес логики в composable функции вполне возможна. Для многих это лучше использования Pinia.
Работа с Reactivity API позволяет делать многое, что не позволяет Pinia - например, делать сторы на TypeScript классах.
Был предложен лайфхак - во время разработки импортировать реактивные данные из composable сторов в Pinia, и тогда возможно использование Devtools. При билде для продакшна Pinia уже нет.
Из-за того, что Pinia оборачивает всё в Reactive (даже в режиме
setup stores
) происходит сильная потеря производительности при работе с Ref или ShallowRef, которые не используют Proxy. Evan You говорил об этом, но плюсы от использования Proxy по сравнению с самописной реализацией реактивности во Vue 2, перекрывают минусы по его словам.Единственный аргумент в пользу Pinia - унификация работы со стором в команде.
Очень интересный пример использования TypeScript классов в качестве стора через composable был предложен пользователем ferferga. Он дает возможность использовать приватные поля, сеттеры и геттеры (без .value), получить first class type support, что было бы невозможно в случае с Pinia. Данный пример приведен здесь не в качестве рекомендации, но как демонстрация того, что возможно с Composition API
Интересная и полезная информация о Vue.js и фронтенде в целом на нашем сайте: Vue‑FAQ.org.
Также заходите на наш Телеграм‑канал: https://t.me/vuefaq
Комментарии (10)
Ar_f
13.12.2023 15:51Плюс pinia над композаблом с глобальным рефом еще и в том, что у пинии явная семантика глобального хранилища, а у композабла это скрыто в реализации. Т.е. никогда не знаешь, этот композабл юзает глобальный стейт или нет. Я как-то решил эту проблему разделением композаблов по папкам: в этой папке композаблы с глобальным стейтом, а в этой с локальным.
А т.к. pinia - это широко известное решение проблемы глобального стейта, то и выглядит оно привлекательней. Меньше шансов нагородить ерунды.
Если приложение маленькое, то можно и на глобальных рефах сделать. Но для большого приложения я бы пожалел тех, кто будет поддерживать код и заюзал бы pinia.
gmtd Автор
13.12.2023 15:51А зачем вам знать, что внутри композабла? Это сервис, у которого есть интерфейс (то, что его функция возвращает), есть грубо говоря документация, описывающая его.
SOLID, буква О - программные сущности должны быть открыты для расширения, но закрыты для изменения. Что внутри них и как работает не должно иметь значения для пользователей этой сущности.
Ar_f
13.12.2023 15:51Мне обычно как-то спокойнее, когда я сразу вижу жизненный цикл объекта. Пиниа стор живет от старта и до закрытия приложения, а "экземпляр" композабла создается каждый раз, как мы его вызываем (условно скажем - в каждом юзающем его компоненте, коих может быть очень много). Может это мои предпочтения, но для меня кажется логичным и удобным такое разделение на синглтон стор и многоинстансный композабл.
gmtd Автор
13.12.2023 15:51Согласен, что тут вопрос привычки и предпочтений разработчика. И то, и то является вполне допустимыми практиками. Поднимая этот вопрос на Реддите я просто хотел выяснить побольше о плюсах и минусах каждого подхода. Примечательно, что там это вызвало на порядок бОльший интерес, чем здесь )
Ar_f
13.12.2023 15:51А аргумент в медленности reactive по сравнению с ref выглядит немного искусственным. Поправьте, если я ошибаюсь. Я не отрицаю, что разница огромная, судя по бэнчмарку. Но в бэнчмарке было сделан 1миллион операций записи. И в худшем случае получили 800 миллисекунд. Это медленно, но в рамках терпимого. Но это же 1 миллион записей. В каком фронтенде может потребоваться миллион раз перезаписать поле в прокси? (Напомню, это глобальный стейт, который по своей сути не должен слишком сильно разрастаться). В реальном приложении я бы ожидал увидеть десятки, может сотни записей в стор за раз. Но поправьте, если я в этом ошибаюсь.
gmtd Автор
13.12.2023 15:51Тесты проводились на элементарных данных.
Теперь представим что мы грузим массив из 1000 продуктов в админку, каждый продукт - объект со многими уровнями вложений данных. Далее, зависимостей от этого массива тоже может быть с десяток в приложении, и каждая из них имеет свои зависимости. То есть, при изменении этого массива, грубо говоря, объем кода, который должен исполниться, растет экспоненциально, а с ним и падает в таком же порядке и производительность
С другой стороны, возьмем не мощный смартфон и висящие у него в фоне программы, которые, тоже ощутимо увеличат разницу в производительности.
На Ref, ShallowRef и других фичах Reactivity API наверняка можно сделать более оптимизированное приложение так, что вышеуказанная разница примет весьма ощутимые размеры.
Ну и не забываем, что Vue, Svelte и другие соревнуются между собой на проценты, кто кого быстрее. Так что даже они важны.
Metotron0
13.12.2023 15:51Я пока что не дочитал документацию до composable — некогда, нужно проект делать :) Когда всё же удаётся почитать документацию, то в новые части проекта добавляются разные штуки, которых раньше не знал. Просто, когда я устраивался на текущую работу, 3-й Vue не так уж сильно давно появился, а когда я стал работать, то стало не до учебников, но новые проекты изредка нужно начинать, а делать их на второй версии не хочется, надо же и третью понемногу осваивать, а то когда же я её освою?
Neoldian
13.12.2023 15:51В продуктовом решении используем пинью и в большом легаси на vue2 - vuex. Но для себя в пет проекте чисто ради эксперимента попробовал юзать композаблы с глобальным стейтом и соглашусь с автором, это бесшовно (refs сразу без приведения) и удобно, можно растащить такие глобальные сторы по функц. модулям, и инициализировать когда чанк первого использующего композабл компонента запустился у клиента(это антипатерн, но как по мне удобно в модульной архитектуре хранить стор внутри модуля). Сразу вспомнилось чем в своё время зацепил, ныне почивший, knockout, там по сути так и реализовывался глобальный стейт ;) Спасибо, тема интересная!
kipy
Вы могли бы не стесняться и указать, что являетесь автором поста на реддите.
gmtd Автор
Не отрицаю, но в тексте статьи это выглядело бы самолюбиво и повлияло на беспристрастное восприятие материала