Удалите всю свою «бизнес-логику» из магазина Vuex, переместив их в обычные функции.
Эта проблема
Если вы используете библиотеку управления состоянием, такую как Redux или Vuex, вы, вероятно, напишете множество методов «действий», содержащих вашу бизнес-логику. (Эта статья применима как к Vuex, так и к Redux. Но, поскольку я работаю с Vuex в последнее время, я буду приводить примеры на Vuex)
Вот общий упрощенный пример определения магазина в Vuex.
// STORE import * as searchApiClient from '@/apiClients/searchApiClient'; ... { namespaced: true, state: { isSearchInProgress: false, searchResults: {}, }, getters: { [getterNames.resultsCount]: (state) => (state.searchResults.results || []).length, }, mutations: { [mutationNames.searchStarted](state) { state.isSearchInProgress = true; state.searchResults = {}; }, [mutationNames.searchCompleted](state, { results }) { state.isSearchInProgress = false; state.searchResults = results; }, }, actions: { async [actionNames.getSearchResultsAsync](context, { query }) { if (!query) { return; } context.commit(mutationNames.searchStarted); const results = await searchApiClient.searchAsync(query); context.commit(mutationNames.searchCompleted, { results }); }, }, }
Чтобы завершить пример, когда у вас есть это хранилище, вы используете его в компоненте Vue следующим образом:
// COMPONENT computed: { ...mapState(moduleNames.search, { searchResults: (state) => state.searchResults.results, isSearchInProgress: (state) => state.isSearchInProgress, }), ...mapGetters(moduleNames.search, [getterNames.resultsCount]), }, methods: { async searchAsync() { await this.$store.dispatch( `${moduleNames.search}/${actionNames.getSearchResultsAsync}`, { query: this.query } ); }, },
Что такое бизнес-логика
Методasync getSearchResultsAsync(context, params)
- это ваша бизнес-логика. Это содержит:
* Проверка
if (!query) {return;}
* Взаимодействие с внешним API
await searchApiClient.searchAsync(query)
* Здесь не отображается, но в основном содержит оркестровку другой бизнес-логики
context.dispatch('tracking/trackAsync', ....)
В приведенном выше примере кода представлена диаграмма зависимостей, подобная этой:
Как видите, очень быстро магазин становится «приложением». Скорее всего, вы глубоко и страстно ненавидите государственные библиотеки, но все равно должны ими пользоваться. Поскольку такая организация кода порождает несколько проблем:
- Сложно читать и следовать
Вы не вызываете «действие» напрямую, а вместо этого вызываетеdispatch
method и даете имя своему «методу действия». Вы полагаетесь на строковое соответствие имен. Между вызывающим кодом и вызываемой функцией нет прямой связи. IDE не может помочь вам перейти к действию. Вам нужно выполнить строковый поиск имени и перейти к правильному совпадению. ❌ - Сложно отлаживать
То же, что и выше, но при отладке. Стек вызовов запутан дополнительными слоями «трубопроводов». ❌ - Магазин - это монстр
Магазин напрямую зависит от клиента внешнего API. Бизнес-логика обычно зависит от доступа к внешнему API и другой бизнес-логики (которая также зависит от доступа к другому внешнему API).
Когда глобальный объект содержит вашу бизнес-логику , он, естественно, становится зависимым от всего этого. ❌ - Сложно тестировать
Вы цените печаль, которую приносит эта проблема «зависимости», когда вы начинаете писать тесты. Попробуйте имитировать свой магазин (или его части) в нетривиальном приложении. Вы начинаете имитировать всю свою систему, даже просто чтобы проверить крошечную функциональность. - Сложно организовать
Бизнес-логика обычно связана с комбинацией различных «подмодулей» вашего «состояния». Поэтому иногда, куда бы вы ни поместили свою бизнес-логику, кажется, что она здесь неуместна. (Подсказка: потому что он принадлежит кому-то другому) ❌
Кроме того, «store» - это ваш большой глобальный одноэлементный объект данных. Он уже содержит все данные в одном объекте. Когда вы вкладываете всю свою логику в этот объект, он становится прекрасным примером дымящегося горячего спагетти / монстра / объекта бога (вы называете его).
Решение
Хранилище - это, по сути, «база данных в памяти», из которой вы пишете и читаете. Библиотеки управления состоянием отлично справляются с задачей подключения механизмов уведомления об изменениях базы данных к компонентам пользовательского интерфейса с помощью mapState
и mapGetters
. Все эти замечательные механизмы используются, и Vuex делает его особенно простым и интуитивно понятным. Продолжайте использовать их, но прекратите использовать actions
. Переместите бизнес-логику на простые старые функции и полностью удалите действия магазина из своей кодовой базы.
Вот как бы это выглядело:
Вот чем мог бы стать приведенный выше пример:
// STORE { namespaced: true, state: () => createInitialState(), getters: { [getterNames.resultsCount]: (state) => (state.searchResults.results || []).length, }, mutations: { [mutationNames.searchStarted](state) { state.isSearchInProgress = true; state.searchResults = {}; }, [mutationNames.searchCompleted](state, { results }) { state.isSearchInProgress = false; state.searchResults = results; }, }, };
Вы бы определили свою бизнес-логику в обычной функции:
// SERVICE import * as searchApiClient from '@/apiClients/searchApiClient'; export async function searchAsync (store, query) { if (!!query == false) { return; } store.commit(mutationNames.searchStarted); const results = await searchApiClient.searchAsync(query); store.commit(mutationNames.searchCompleted, { results }); };
Кодируйте всю свою логику как можно проще и взаимодействуйте с вашим магазином (базой данных) с помощью мутаций (это своего рода шаблон «репозиторий»). Чисто, просто и аккуратно.
Чтобы завершить пример, вы должны использовать магазин и службы в своем компоненте Vue следующим образом:
// COMPONENT import * as searchService from '@/services/searchService'; computed: { ...mapState(moduleNames.search, { searchResults: (state) => state.searchResults.results, isSearchInProgress: (state) => state.isSearchInProgress, }), ...mapGetters(moduleNames.search, [getterNames.resultsCount]), }, methods: { async searchAsync() { await searchService.searchAsync(this.$store, this.query); }, },
Хорошо, преимущества:
- Легче читать и следовать
Нет соответствия строк. Вызов бизнес-логики из компонента прямой. IDE снова ваш друг. Это позволит вам перейти к определению, отобразить IntelliSense и параметры. Все хорошее. Также прямые обращения к другим «методам бизнес-логики». Навигация по коду снова вернулась к своему положению. ✔️ - Легче отлаживать
Нет слоев трубопроводов. Звонки прямые. Стек вызовов чистый. ✔️ - Магазин намного (намного) проще
Он не зависит от внешних вызовов API или сложных взаимодействий внутри методов действий. (Имейте в виду, если логика вашего бизнеса по своей сути сложна, эта базовая сложность не исчезнет просто потому, что вы переместили ее из одного места в другое 😃 но, по крайней мере, вашим «магазином» будет легче управлять ) ✔️ - Легче тестировать
Мы уменьшили хранилище до объекта в памяти без внешней зависимости. Больше не нужно издеваться над магазином. Для каждого теста вы можете создать копию исходного магазина и использовать его напрямую. Когда вам нужно имитировать методы бизнес-логики, вы можете имитировать их более простыми способами, используя стандартные утилиты вашей библиотеки тестирования. ✔️ - Организовать стало проще
Теперь, когда функциям бизнес-логики не нужно располагаться в магазине рядом с кучей данных, не стесняйтесь организовывать их любым удобным для вас способом. ✔️
Обратной стороной?
export async function searchAsync (store, query) {
Вам необходимо передать экземпляр вашего магазина в метод бизнес-логики. Добавление этого дополнительного параметра к каждому методу может показаться повторяющимся и скучным. Не стесняйтесь воспринимать это как проблему и как-то решать ее с помощью механизма «глобального поставщика хранилища» или механизма «внедрения зависимостей параметров».
Мне нравится, когда это откровенно. Тестировать тоже легче. Так что я не вижу в этом недостатка. Код пишется один раз, а читается сто раз. Я оптимизирую для большего блага.
Вот рабочий пример: https://github.com/veyselozdemir/vuex-no-actions
Заключение
Что ж, большое спасибо за то, что дочитали до этого места. Вы прочитали более 1000 слов. Это достижение 😃
Вывод: не используйте действия.
Дополнительные выводы. Держите свои магазины компактными и скупыми. Ограничьте взаимодействие геттерами и мутациями (сеттерами). Напишите свою бизнес-логику обычными методами. Если у вас есть зависимость от хранилища, передайте хранилище в качестве явного параметра вместо использования волшебных библиотек конвейеров.
TL; DR
Удалите всю свою «бизнес-логику» из магазина Vuex, переместив их в простые старые функции и удалите все свои action
методы из магазина. Ваш код будет легче писать, легче тестировать и, что самое главное, легче читать.
Если вы хотите сразу перейти к образцу: https://github.com/veyselozdemir/vuex-no-actions
Больше контента на plainenglish.io