Да-да, я знаю, Vue 3 находится в stable-версии, и даже Nuxt наконец-то обновился. Но именно 3-й Vue с его provider
/ inject
подтколкнул меня к поиску решения о том, как можно удобно инкапсулировать бизнес-логику во Vue 2.
Введение
Начнем с вопроса «Почему нельзя просто обновиться до Vue 3?». Все просто, некоторые проекты обладают таким уровнем связанности с другими проектами, что это невозможно сделать быстро. Где-то возможно используются специфичные пакеты, которые были написаны только под 2-ю версию Vue. Где-то разработчиков устраивает Vue 2, а даже если бы они и перешли на Vue 3, они продолжали бы писать на Options API (скажу сразу, я не проверяла свое решение на Vue 3, так что не уверена сработает ли такой подход там, но вообще должен).
Как можно инкапсулировать логику
На просторах интернета и участвуя в вопросах, задаваемых в чатах (по типу «Vue.js — русскоговорящее сообщество»), да и просто работая, чаще всего я видела несколько решений. Давайте разберем их плюсы и минусы.
Вынести во Vuex/Pinia/Другой стейт менеджер
На удивление один из самых популярных ответов, как только нужно делиться какими-то данными — вынеси в стор.
Доходит до смешного, человек спрашивал как можно передать строку поиска из компонента в соседний (у них общий родитель). Ему накинули несколько решений, и человек выбрал Vuex, потому что на его взгляд это проще.
Другой пример, на проекте я видела страницу, которая сохраняла временные данные форм (и в какой-то мере кешировала на сессию) с помощью.. Vuex. Я думаю, нет смысла объяснять подробно чем это чревато, но багов там связанных с этим было много, мне пришлось полностью переделать эту страницу.
Pros
-
Часто советуют, легко найти помощь
-
Использование vuex интуитивно понятно
-
Возможно внушает чувство безопасности
-
В целом легко читается, понятно откуда приходят данные
-
Можно получить только те данные, которые тебе нужны в конкретном компоненте
Cons
-
Вызывает баги, если пытаться хранить временные данные (которые должны чиститься, или меняться в зависимости от контекста)
-
Невозможно переиспользовать логику (модуль один на сайт, от этого ты никуда не денешься, дополнительный экземпляр не создашь)
только если не сделать 10 одинаковых модулей -
Используя недолго эту логику, она все равно останется в памяти, хоть и больше не требуется
-
Данные не защищены, любой компонент может изменить их с помощью прямого обращения к
state
Миксины
Это второй по популярности метод выноса какой-то общей логики, но если ответ про Vuex частый при проблеме распространения данных, то миксины — это популярный метод выноса логики. Хотя по моим впечатлениям люди начали реже им пользоваться, в силу того что в официальной библиотеке рекомендуют максимально остерегаться этого способа.
Pros
-
опять же интуитивно понятно, выглядит как кусок компонента
-
другого способа интегрировать общий хук, или фреймворко-зависимые элементы особо нет
Cons
-
Если само написание миксина действительно интуитивно понятно, то вот чтение компонента, использующего миксин совершенно нечитабельно. Ты выделяешь переменную, ищешь ее по файлу, а ее просто нет, и вот эту маленькую строчку
mixins
очень легко пропустить. -
Легко затереть функциональность миксина, используя случайно те же имена свойств/методов
-
Иногда миксины используют свойства, которые должны быть определены в компоненте, а ты можешь этого не знать
-
Невозможно расшарить данные (
только если не положить их во vuex) -
Подходит только для переиспользования кода, что-то глобальное сделать с помощью миксина не получится
-
Выбрать какие методы/свойства тебе нужны нельзя
Provide / Inject
Это уже не самый популярный способ, не все знают про эту опцию. Многие знают про то, что это есть во Vue 3, но во Vue 2 provide появился еще в версии 2.2.0. Подробнее можно почитать по ссылке.
В целом технология позволяет передать либо объект, либо функцию, возвращающую объект. Так что думаю есть возможность, если поиграться передать тот же экземпляр класса. Но возможно это вызовет определенные проблемы.
Pros
-
Позволяет передавать данные на любую глубину
-
Можно выбрать, что ты будешь инъектить
Cons
-
Требует общего родителя, в худшем случае придется делать это на странице или в
App
что засоряет код -
Таким образом не получится переиспользовать код, это больше про распространение данных
-
Если сделать экземпляр класса (каким-то образом, я не пробовала), то ты его себе заберешь целиком, нет возможности выбрать, что тебе надо
-
Не интуитивно и не очевидно, что будет происходить с реактивностью, где менять эти данные, и вообще вызывает много вопросов
Почему я упомянула provide/inject в начале
Когда я писала на Vue 2, у меня даже примерной идеи не было, как бы можно было инкапсулировать логику. Мне казалось, что в том формате, как мы делаем компоненты, это будет по-любому неудобно, непонятно, и как-то костыльно.
Vue 3 с Composition API открыл для меня новую веху, там можно сделать экземпляр класса, полностью обернуть его в reactive, сделать provide
прям на этапе создания приложения (т.е. в index.js
условном, что было понятно).
Но и это решение меня не до конца устраивало, мне хотелось сделать это как-то по-другому. Пока я об этом думала, я перешла на другой проект, который написан на Vue 2, и это подтолкнуло меня к поиску решения.
Вынести в функции
Этот способ очень хорошо подходит для простых функций, не связанных ни с чем другим, валидаторы или форматтеры. С точки зрения инкапсуляции логики (а обычно в таком случае мы говорим про связный процесс, где происходит несколько этапов в разное время) это не самый подходящий способ.
Pros
-
Легко использовать, импортируешь функцию и используешь
-
Знакомо и узнаваемо
Cons
-
Для связанного процесса приходится передавать данные из одной функции в другую, что ухудшает читабельность
-
Если пытаешься сохранить данные в какой-то переменной вне функций, не будет ясности, что там находится
Глобальные переменные
Во Vue 2 я частенько записывала созданный мной класс, хранящий экземпляр Axios, в глобальные переменные. Это довольно удобно для модуля, который используется повсеместно.
Pros
-
Удобное использование через
this
-
Можно в глобальную переменную записать экземпляр класса, что увеличивает ваши возможности
Cons
-
Даже если записать экземпляр класса, то он будет один
-
Непонятно, будут ли данные реактивными, не совсем очевидно
-
Использование через
this
удобно для тех, кто знает, какие данные в проекте глобальные. Для остальных придется еще догадаться, где искать эту запись, и что же кроется под переменной.
Вынести в класс
Кажется самым удобным методом, но те статьи, которые я находила, предлагали либо сделать singleton, либо делать статические методы и встраивать их подобным образом
methods: { someAlias() { Class.someMethodFromClass(); } }
Такой способ мне не нравится тем, что много мусорных функций, нет понимания как встраивать данные и менять их. Да и вообще так получилось, что я не нашла ни одной статьи, которая бы показывала полноценное решение, учитывая всевозможные случаи.
Поэтому я решила создать это решение сама.
Вводные
Хочется, чтобы можно было создать сервис, который
-
Создавал экземпляр только по запросу, позволял создать несколько экземпляров, мог удалить нужный экземпляр
-
Было понятно откуда пришли данные или методы, чтобы при поиске по файлу названия можно было найти источник
-
Данные должны быть консистентные, при изменении в одном месте, во всех остальных местах они должны стать такими же
-
Если данные используются в компоненте, то должна быть возможность сделать их реактивными, то есть при отображении в template после изменения компонент должен перерендериться
-
Должна быть возможность защитить данные, чтобы менять их можно было только из класса, запретить изменение из компонентов
-
Должна быть возможность менять данные с компонента, установить валидатор для подобных изменений
-
Должна быть возможность получить экземпляр компонента для каких-то специфичных действий,
хоть я и не очень это поддерживаю -
Должна быть возможность встраивать этот класс в любой компонент без обязательства иметь общего родителя
Спойлер
Я смогла реализовать подобную логику, оказалось, это было так просто, что даже смешно. И теперь я хочу рассказать об этом вам и предоставить функции-фабрики для обвязки подобного класса, чтобы вы могли тоже удобно пользоваться классами.
Это 1 часть статьи про реализацию сервиса во Vue 2.
Во 2 части я хочу рассказать о деталях проектирования класса, как работать с разными типами данных, каким образом они встраиваются в компонент и приобретают реактивность, как сделать геттер на свойство/свойства (аналог computed
) и передать его в компонент.
В 3 части я расскажу про экземпляры, как регулировать создание и уничтожение, покажу мои функции-фабрики для обвязки класса, как сделать удобную передачу таких свойств в компонент.
Опционально
В 4 части я бы хотела порассуждать о том, какой сервис можно считать хорошим, что стоит выносить в сервис, чтобы не выстрелить себе потом в ногу, как их тестировать и как тестировать компоненты, использующие сервис.
Буду смотреть по вашей реакции, будет ли вам интересно про это прочитать.
Если не хотите ждать, то вот ссылка на репозиторий с рабочим решением, там есть подробные комментарии о реализации. Наиболее интересные файлы: сам класс, и конечно функции для обвязки.
Этот вариант еще в стадии черновика, я готовлю для него документацию. С практикой я буду его дорабатывать, плюс по ходу написания следующих частей я могу вспомнить про какой-то кейс, который не учла.
ссылка на оригинал статьи https://habr.com/ru/articles/700392/
Добавить комментарий