Также прочитайте другие мои рассказы, например:
- Узнайте, как рефакторинг однофайловых компонентов Vue.js на реальном примере
- Что делает хороший проект разработки?
- Составление вычисляемых свойств в Vue.js

При написании крупномасштабных приложений управление состоянием внешнего интерфейса может быть довольно сложным. Например, для приложений Vue.js существует плагин под названием Vuex, который предлагает очень простое управление состоянием и предлагает использовать следующую структуру приложения:

Если вам интересен пример, поищите пример корзины покупок в официальном репозитории Vuex (vuejs / vuex - shopping-cart) или созданный мной пример («igeligel / vuex-simple- состав").

Это действительно отлично работает, потому что у нас есть простые модули Vuex, содержащие действия, геттеры и мутации в рамках этого модуля. Общие действия, геттеры или мутации сохраняются непосредственно в каталоге магазина. Затем все эти модули, глобальные действия, геттеры и мутации импортируются в файл index.js и снова экспортируются в конструкторе модуля Vuex. Тем не менее, проблема может возникнуть, когда вы получите все больше и больше модулей, что действительно характерно для больших приложений. Представьте себе такое приложение, как GitLab. Он достаточно большой, чтобы вместить множество модулей. Например, боковая панель представления репозитория GitLab выглядит примерно так:

Каждый пункт меню будет в основном модулем, содержащим несколько действий, геттеров и мутаций. Все эти части просто перечислены в одном файле модуля. На самом деле это не будет хорошо масштабироваться, поскольку даже модули могут стать довольно большими с учетом того, сколько функций требуется модулю, и в результате модули будут иметь более 1000 строк кода.

Но решение этой проблемы есть. Мы могли просто извлечь действия, геттеры и мутации в каталогах модулей. Глобальные действия, геттеры или мутации могут находиться непосредственно в каталоге магазина. Структура приложения будет выглядеть так:

По сути, у вас все еще будет возможность использовать глобальные действия, геттеры и мутации, но я бы не рекомендовал это, поскольку в этом нет необходимости. При таком подходе у нас будет несколько отдельных файлов. Все действия, геттеры и мутации модуля чата будут импортированы индексом внутри каталога чата. Затем этот модуль будет импортирован в глобальное хранилище. Важно отметить, что вы должны установить опцию пространства имен внутри модулей, чтобы у вас было правильное пространство имен. Это делается в файле store/index.js:

import Vue from 'vue';
import Vuex from 'vuex';
import chatModule from './modules/chat/index';
import productsModule from './modules/products/index';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    chat: chatModule,
    products: productsModule,
  },
});

Внутри этого магазина у нас есть два модуля: chat и products. Оба модуля содержат действия, геттеры и мутации и импортируются в главный файл модуля index.js модуля, а затем снова экспортируются. Наконец, экспортированные данные могут использоваться модулем магазина.

Это позволит зарегистрировать модули, и код будет разделен таким образом, чтобы его можно было читать, перемещать и поддерживать. Хороший пример этой реализации можно найти внутри bstavroulakis / vue-wordpress-pwa или в моей реализации igeligel / vuex-namespaced-module-structure. Эта структура приложения действительно хорошо справляется с приложениями малого и среднего размера. Новичку в кодовой базе не составит труда найти места, где живет бизнес-логика, поскольку каждый модуль должен иметь собственное имя и ссылку внутри компонентов. Работать с модулями действительно интересно, и это объяснено в официальной документации.

Однако в какой-то момент возникает проблема. Ваша бэкэнд-команда создает гораздо больше API, а приложение становится все более и более сложным. Вы достигнете 20, 30 или 50 модулей. По-прежнему обслуживается, но ваш новый стажер борется с архитектурой, так как не знает, где вызывается бизнес-логика. Затем вы спрашиваете себя, как лучше это структурировать. Вы, вероятно, могли бы выполнять вызовы API непосредственно в компонентах, но это было бы огромным беспорядком, поскольку в этом случае компоненты будут содержать бизнес-логику. Компоненты должны присутствовать только для визуализации данных. Не обрабатывает данные.

В React есть понятие контейнеров и компонентов. Vue.js не обеспечивает его строгого соблюдения. Контейнеры - это просто компоненты, но они также могут получать данные из магазина и общаться с магазином. Компоненты нужны только для хранения данных и их рендеринга. Они общаются с этими верхними контейнерами через подпорки. Представим себе виджет чата внутри нашего приложения, которому нужно получать какие-то данные из магазина или даже лучше из API. Мы просто создадим простой пример, получая все сообщения из чата без поддержки в реальном времени. Предположим, у нас есть некий контейнер, в котором хранится весь чат. Этот контейнер будет связываться с магазином для обновления данных или для заполнения данных в компонентах презентации. Вся архитектура показана на этом небольшом графике:

В этой системе у нас есть контейнер с именем Chat.vue, который связывается с нашим модулем магазина chat. Этот модуль чата также обрабатывает логику, вызывая API и обновляя магазин. Когда состояние окончательно обновит контейнер, Chat.vue также будет обновлен с использованием вычисляемого свойства, которое будет обновляться в соответствии с реактивной природой Vue.js и Vuex. После этого собственность будет передана ChatList.vue в качестве реквизита. Поскольку свойства являются массивом в этом компоненте, будет выполняться итерация, которая будет отображать список из ChatListElement.vue компонентов, которые отвечают за отображение сообщений чата и метаинформации.

С помощью этого шаблона мы разделили приложение на три части. Одна часть - это бизнес-логика, которая существует внутри модуля магазина или, в более общем смысле, только в магазине, элементы контейнера, которые отвечают за получение данных и их заполнение в компонентах представления, которые существуют только для визуализации данных. Это предлагает нам большую модульность и поддерживает принцип единой ответственности. Это также обеспечивает хорошую тестируемость, поскольку вы можете тестировать каждую часть этой структуры отдельно. Вместе они сформируют своего рода интеграционный тест. Но об этом можно поговорить в другой статье.

А теперь представьте, что приложение сильно разрастается. Под большим я подразумеваю, что у вас есть несколько модулей, и неясно, где эти модули больше используются, какие компоненты от них зависят, а какие нет. В огромных приложениях это может стать настоящей проблемой. Представьте, что кто-то новичок в кодовой базе упускает из виду 50 модулей и где-то около 50 компонентов. У него была бы большая проблема сориентироваться.

Vuex рекомендует иметь каталоги функций бизнес-логики внутри каталога магазина. Иногда соединение с контейнерами, которые используют эти модули, может быть прервано, и становится неясно, где используются эти модули Vuex. Некоторые модули могут присутствовать только из-за одного контейнера, поэтому было бы хорошо иметь эту бизнес-логику где-то рядом с контейнером, который будет обрабатывать данные. Давайте немного реструктурируем приложение. Этот шаблон основан на vuejs-templates / webpack.

Единственная разница в том, что я установил Vuex в этот шаблон, настроил его и добавил каталог модулей под каталогом src. Вы можете найти это приложение позже в этом сообщении блога. Отличие этого каталога в том, что он содержит модули. Не путайте эти модули с модулями Vuex. Вероятно, есть лучшее имя, поэтому, если вы его знаете, прокомментируйте его в этой статье. Итак, внутри каталога модулей у нас есть модули этого приложения Vue.js. Выглядит это примерно так:

Внутри каталога модулей есть несколько каталогов, описывающих различные функции. Например, у нас есть функция чата и продуктов. Однако самое интересное находится внутри этих каталогов модулей. У нас есть каталог магазина, index.vue файл и компоненты. Чтобы прояснить ситуацию, мы просто посмотрим на файлы Single File Component. index.vue используется как компонент контейнера. Этот контейнер будет извлекать все данные из хранилища и передавать эти данные как props компонентам. Компоненты ChatList.vue и ChatListElement.vue нужны только для того, чтобы извлекать данные из компонента и запускать действия в хранилище, которое глобально привязано к экземпляру Vue.js. Большой вопрос в том, почему этих компонентов нет в каталоге компонентов. Причина в том, что эти компоненты специально созданы для этой функции. Если бы они были повторно использованы для другой функции, я бы подумал о перемещении ее в каталог компонентов. В основном вопрос здесь в том, используется ли компонент каким-либо образом повторно. Затем мы должны реорганизовать компонент в общий каталог компонентов. Теперь идет магазин. Это в основном то же хранилище, что и в другом шаблоне, но перемещенное в хранилище локальных каталогов. Чтобы зарегистрировать его, мы используем функцию registerModule из Vuex. Эта функция динамически зарегистрирует модуль Vuex. Обычно он используется для плагинов, но мы будем использовать его здесь для лучшего разделения проблем. Внутри файла index.vue мы можем получить доступ к функциям жизненного цикла с помощью Vue.js,, а внутри функции created мы можем безопасно создать наш модуль.

import { mapGetters } from 'vuex';
import store from './_store';
import ChatList from './_components/ChatList';

export default {
  name: 'ChatModule',
  components: {
    ChatList,
  },
  computed: {
    ...mapGetters({
      messages: '$_chat/messages',
    }),
  },
  created() {
    this.$store.registerModule('$_chat', store);
  },
  mounted() {
    this.$store.dispatch('$_chat/getMessages');
  },
};

Мы ставим перед ним префикс $_, чтобы показать, что этот модуль является частным, поскольку он доступен только внутри модуля. После регистрации магазин будет перенесен в наш глобальный магазин Vuex. С этого момента мы можем использовать эти функции Vuex внутри наших компонентов. Чтобы зарегистрировать магазин, нам нужно каким-то образом привязать функциональность Vuex к экземпляру Vue.js. Это легко сделать, создав пустое хранилище Vuex, экспортировав его и добавив в конструктор Vue.js. Посмотрите эти файлы, чтобы понять (store / index.js, main.js).

Если нам понадобится какое-то глобальное хранилище, я бы создал модуль Vuex в каталоге хранилища с рекомендуемой структурой. Если, например, нам нужно пройти аутентификацию в разных местах внутри приложения, было бы лучше, чтобы он был доступен для общего доступа таким образом, который не связан с контейнерами. Это было бы хорошим вариантом использования общего модуля Vuex.

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

Хорошим аргументом в пользу этой структуры является то, что модули каким-то образом извлекаются. Если функция становится слишком большой, вы можете просто извлечь ее, создав модуль из его каталога внутри каталога src/modules и сделав из него пакет npm. Единственное, что вам нужно будет экспортировать, - это компонент контейнера. Затем этот пакет npm можно разместить в реестре вашей компании или публично на npm. Просто не забудьте как-нибудь настроить поведение модуля Vuex. Еще один хороший аргумент - то, что тесты могут быть написаны с ограниченным набором функций.

Наиболее положительным аргументом является то, что объем модуля Vuex, контейнеров и компонентов понятен каждому разработчику, читающему код. Вы можете очень быстро найти бизнес-логику каждой функции, а функции легко протестировать, поскольку принцип разделения задач используется во всем приложении.

Примеры различных структур:

Все еще здесь?

Возможно, вам понравится читать:
- Узнайте, как рефакторинг однофайловых компонентов Vue.js на реальном примере
- Что делает проект разработки красивым?
- Составление вычисленных свойства в Vue.js

Спасибо, что прочитали эту статью. Я ценю это 🙂 Вы можете подписаться на меня в twitter или зайти на мой личный сайт, где вы можете найти больше статей в блоге и немного больше обо мне и моих проектах.

Эта запись в блоге написана в сотрудничестве с компанией 3YOURMIND. Ищем разработчиков в Берлине. Вы можете найти вакансии здесь. Мы - стартап по 3D-печати, в котором много крутых инженеров, использующих Vue.js, Django REST, Java, Docker и многие другие фреймворки, которые изменят мир корпоративной 3D-печати.