На Reddit прошла интересная дискуссия с 25К+ просмотрами по вопросу предпочтений разработчиков при необходимости управлять глобальным состоянием во Vue 3. Ниже её итоги.

Reddit подводит итоги года по частоте посещения /r/vuejs из разных стран мира
Reddit подводит итоги года по частоте посещения /r/vuejs из разных стран мира

Вопрос автором был поставлен так: Зачем использовать Pinia вместо глобальных ref's?

В своих проектах я использую composable функции с глобальным состоянием, как описано в документации Vue.

Каждая функция представляет собой объект бизнес-логики - например, useShoppingCart, useAppConfig и т. д. - и инкапсулирует реактивное состояние и бизнес-логику.

Этот подход часто критикуют и рекомендуют использовать Pinia.

Я знаю о поддержке SSR и возможностях Devtools в Pinia, но это не мой случай - они мне не нужны.

Так почему же, за исключением нужды в SSR и Devtools, я должен использовать Pinia?

Плюсы composable сторов на глобальных рефах очевидны:

  1. Простота

  2. Нативность по отношению к фреймворку.

  3. Отсутствие зависимостей означает отсутствие будущей ситуации "RIP Vuex" с переписыванием 50% кодовой базы проекта.

  4. API Composition выглядит очень зрелым и стабильным и вряд ли сильно изменится в ближайшем будущем (по сравнению с переходом Vue 2 -> Vue 3).

  5. Позволяет использовать всю мощь Reactivity API вместо жесткой Reactive обертки для переменных у Pinia. Разница в производительности может быть огромной.

Минусы?

Было получено на данный момент 36 комментариев, которые можно скомпилировать в следующие выводы:

  1. Большинство согласилось, что если не нужна поддержка SSR и интеграция с Devtools, то работа с Reactivity API напрямую и инкапсуляция реактивного состояния и бизнес логики в composable функции вполне возможна. Для многих это лучше использования Pinia.

  2. Работа с Reactivity API позволяет делать многое, что не позволяет Pinia - например, делать сторы на TypeScript классах.

  3. Был предложен лайфхак - во время разработки импортировать реактивные данные из composable сторов в Pinia, и тогда возможно использование Devtools. При билде для продакшна Pinia уже нет.

  4. Из-за того, что Pinia оборачивает всё в Reactive (даже в режиме setup stores) происходит сильная потеря производительности при работе с Ref или ShallowRef, которые не используют Proxy. Evan You говорил об этом, но плюсы от использования Proxy по сравнению с самописной реализацией реактивности во Vue 2, перекрывают минусы по его словам.

  5. Единственный аргумент в пользу Pinia - унификация работы со стором в команде.

Очень интересный пример использования TypeScript классов в качестве стора через composable был предложен пользователем ferferga. Он дает возможность использовать приватные поля, сеттеры и геттеры (без .value), получить first class type support, что было бы невозможно в случае с Pinia. Данный пример приведен здесь не в качестве рекомендации, но как демонстрация того, что возможно с Composition API


Интересная и полезная информация о Vue.js и фронтенде в целом на нашем сайте: Vue‑FAQ.org.

Также заходите на наш Телеграм‑канал: https://t.me/vuefaq

Комментарии (10)


  1. kipy
    13.12.2023 15:51

    Вы могли бы не стесняться и указать, что являетесь автором поста на реддите.


    1. gmtd Автор
      13.12.2023 15:51

      Не отрицаю, но в тексте статьи это выглядело бы самолюбиво и повлияло на беспристрастное восприятие материала


  1. Ar_f
    13.12.2023 15:51

    Плюс pinia над композаблом с глобальным рефом еще и в том, что у пинии явная семантика глобального хранилища, а у композабла это скрыто в реализации. Т.е. никогда не знаешь, этот композабл юзает глобальный стейт или нет. Я как-то решил эту проблему разделением композаблов по папкам: в этой папке композаблы с глобальным стейтом, а в этой с локальным.

    А т.к. pinia - это широко известное решение проблемы глобального стейта, то и выглядит оно привлекательней. Меньше шансов нагородить ерунды.

    Если приложение маленькое, то можно и на глобальных рефах сделать. Но для большого приложения я бы пожалел тех, кто будет поддерживать код и заюзал бы pinia.


    1. gmtd Автор
      13.12.2023 15:51

      А зачем вам знать, что внутри композабла? Это сервис, у которого есть интерфейс (то, что его функция возвращает), есть грубо говоря документация, описывающая его.

      SOLID, буква О - программные сущности должны быть открыты для расширения, но закрыты для изменения. Что внутри них и как работает не должно иметь значения для пользователей этой сущности.


      1. Ar_f
        13.12.2023 15:51

        Мне обычно как-то спокойнее, когда я сразу вижу жизненный цикл объекта. Пиниа стор живет от старта и до закрытия приложения, а "экземпляр" композабла создается каждый раз, как мы его вызываем (условно скажем - в каждом юзающем его компоненте, коих может быть очень много). Может это мои предпочтения, но для меня кажется логичным и удобным такое разделение на синглтон стор и многоинстансный композабл.


        1. gmtd Автор
          13.12.2023 15:51

          Согласен, что тут вопрос привычки и предпочтений разработчика. И то, и то является вполне допустимыми практиками. Поднимая этот вопрос на Реддите я просто хотел выяснить побольше о плюсах и минусах каждого подхода. Примечательно, что там это вызвало на порядок бОльший интерес, чем здесь )


  1. Ar_f
    13.12.2023 15:51

    А аргумент в медленности reactive по сравнению с ref выглядит немного искусственным. Поправьте, если я ошибаюсь. Я не отрицаю, что разница огромная, судя по бэнчмарку. Но в бэнчмарке было сделан 1миллион операций записи. И в худшем случае получили 800 миллисекунд. Это медленно, но в рамках терпимого. Но это же 1 миллион записей. В каком фронтенде может потребоваться миллион раз перезаписать поле в прокси? (Напомню, это глобальный стейт, который по своей сути не должен слишком сильно разрастаться). В реальном приложении я бы ожидал увидеть десятки, может сотни записей в стор за раз. Но поправьте, если я в этом ошибаюсь.


    1. gmtd Автор
      13.12.2023 15:51

      Тесты проводились на элементарных данных.

      Теперь представим что мы грузим массив из 1000 продуктов в админку, каждый продукт - объект со многими уровнями вложений данных. Далее, зависимостей от этого массива тоже может быть с десяток в приложении, и каждая из них имеет свои зависимости. То есть, при изменении этого массива, грубо говоря, объем кода, который должен исполниться, растет экспоненциально, а с ним и падает в таком же порядке и производительность

      С другой стороны, возьмем не мощный смартфон и висящие у него в фоне программы, которые, тоже ощутимо увеличат разницу в производительности.

      На Ref, ShallowRef и других фичах Reactivity API наверняка можно сделать более оптимизированное приложение так, что вышеуказанная разница примет весьма ощутимые размеры.

      Ну и не забываем, что Vue, Svelte и другие соревнуются между собой на проценты, кто кого быстрее. Так что даже они важны.


  1. Metotron0
    13.12.2023 15:51

    Я пока что не дочитал документацию до composable — некогда, нужно проект делать :) Когда всё же удаётся почитать документацию, то в новые части проекта добавляются разные штуки, которых раньше не знал. Просто, когда я устраивался на текущую работу, 3-й Vue не так уж сильно давно появился, а когда я стал работать, то стало не до учебников, но новые проекты изредка нужно начинать, а делать их на второй версии не хочется, надо же и третью понемногу осваивать, а то когда же я её освою?


  1. Neoldian
    13.12.2023 15:51

    В продуктовом решении используем пинью и в большом легаси на vue2 - vuex. Но для себя в пет проекте чисто ради эксперимента попробовал юзать композаблы с глобальным стейтом и соглашусь с автором, это бесшовно (refs сразу без приведения) и удобно, можно растащить такие глобальные сторы по функц. модулям, и инициализировать когда чанк первого использующего композабл компонента запустился у клиента(это антипатерн, но как по мне удобно в модульной архитектуре хранить стор внутри модуля). Сразу вспомнилось чем в своё время зацепил, ныне почивший, knockout, там по сути так и реализовывался глобальный стейт ;) Спасибо, тема интересная!